xref: /illumos-gate/usr/src/cmd/picl/plugins/sun4v/lib/snmp/snmplib.c (revision 936b7af69172dce89b577831f79c0e18d15e854b)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * The snmp library helps to prepare the PDUs and communicate with
31  * the snmp agent on the SP side via the ds_snmp driver.
32  */
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <thread.h>
39 #include <synch.h>
40 #include <errno.h>
41 #include <sys/time.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <fcntl.h>
45 #include <libnvpair.h>
46 #include <sys/ds_snmp.h>
47 
48 #include "libpiclsnmp.h"
49 #include "snmplib.h"
50 #include "asn1.h"
51 #include "pdu.h"
52 #include "debug.h"
53 
54 #pragma init(libpiclsnmp_init)		/* need this in .init */
55 
56 /*
57  * Data from the MIB is fetched based on the hints about object
58  * groups received from (possibly many threads in) the application.
59  * However, the fetched data is kept in a common cache for use across
60  * all threads, so even a GETBULK is issued only when absolutely
61  * necessary.
62  *
63  * Note that locking is not fine grained (there's no locking per row)
64  * since we don't expect too many MT consumers right away.
65  *
66  */
67 static mutex_t	mibcache_lock;
68 static nvlist_t	**mibcache = NULL;
69 static uint_t	n_mibcache_rows = 0;
70 
71 static mutex_t snmp_reqid_lock;
72 static int snmp_reqid = 1;
73 
74 #ifdef SNMP_DEBUG
75 uint_t snmp_nsends = 0;
76 uint_t snmp_sentbytes = 0;
77 uint_t snmp_nrecvs = 0;
78 uint_t snmp_rcvdbytes = 0;
79 #endif
80 
81 #ifdef USE_SOCKETS
82 #define	SNMP_DEFAULT_PORT	161
83 #define	SNMP_MAX_RECV_PKTSZ	(64 * 1024)
84 #endif
85 
86 /*
87  * Static function declarations
88  */
89 static void	libpiclsnmp_init(void);
90 
91 static int	lookup_int(char *, int, int *, int);
92 static int	lookup_str(char *, int, char **, int);
93 static int	lookup_bitstr(char *, int, uchar_t **, uint_t *, int);
94 
95 static oidgroup_t *locate_oid_group(struct picl_snmphdl *, char *);
96 static int	search_oid_in_group(char *, char *, int);
97 
98 static snmp_pdu_t *fetch_single(struct picl_snmphdl *, char *, int, int *);
99 static snmp_pdu_t *fetch_next(struct picl_snmphdl *, char *, int, int *);
100 static void	fetch_bulk(struct picl_snmphdl *, char *, int, int, int, int *);
101 static int	fetch_single_str(struct picl_snmphdl *, char *, int,
102 		    char **, int *);
103 static int	fetch_single_int(struct picl_snmphdl *, char *, int,
104 		    int *, int *);
105 static int	fetch_single_bitstr(struct picl_snmphdl *, char *, int,
106 		    uchar_t **, uint_t *, int *);
107 
108 static int	snmp_send_request(struct picl_snmphdl *, snmp_pdu_t *, int *);
109 static int	snmp_recv_reply(struct picl_snmphdl *, snmp_pdu_t *, int *);
110 
111 static int	mibcache_realloc(int);
112 static void	mibcache_populate(snmp_pdu_t *, int);
113 static char	*oid_to_oidstr(oid *, size_t);
114 
115 
116 static void
117 libpiclsnmp_init(void)
118 {
119 	(void) mutex_init(&mibcache_lock, USYNC_THREAD, NULL);
120 	if (mibcache_realloc(0) < 0)
121 		(void) mutex_destroy(&mibcache_lock);
122 
123 	(void) mutex_init(&snmp_reqid_lock, USYNC_THREAD, NULL);
124 
125 	LOGINIT();
126 }
127 
128 picl_snmphdl_t
129 snmp_init()
130 {
131 	struct picl_snmphdl	*smd;
132 #ifdef USE_SOCKETS
133 	int	sbuf = (1 << 15);	/* 16K */
134 	int	rbuf = (1 << 17);	/* 64K */
135 	char	*snmp_agent_addr;
136 #endif
137 
138 	smd = (struct picl_snmphdl *)calloc(1, sizeof (struct picl_snmphdl));
139 	if (smd == NULL)
140 		return (NULL);
141 
142 #ifdef USE_SOCKETS
143 	if ((snmp_agent_addr = getenv("SNMP_AGENT_IPADDR")) == NULL)
144 		return (NULL);
145 
146 	if ((smd->fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
147 		return (NULL);
148 
149 	(void) setsockopt(smd->fd, SOL_SOCKET, SO_SNDBUF, &sbuf, sizeof (int));
150 	(void) setsockopt(smd->fd, SOL_SOCKET, SO_RCVBUF, &rbuf, sizeof (int));
151 
152 	memset(&smd->agent_addr, 0, sizeof (struct sockaddr_in));
153 	smd->agent_addr.sin_family = AF_INET;
154 	smd->agent_addr.sin_port = htons(SNMP_DEFAULT_PORT);
155 	smd->agent_addr.sin_addr.s_addr = inet_addr(snmp_agent_addr);
156 #else
157 	smd->fd = open(DS_SNMP_DRIVER, O_RDWR);
158 	if (smd->fd < 0) {
159 		free(smd);
160 		return (NULL);
161 	}
162 #endif
163 
164 	return ((picl_snmphdl_t)smd);
165 }
166 
167 void
168 snmp_fini(picl_snmphdl_t hdl)
169 {
170 	struct picl_snmphdl	*smd = (struct picl_snmphdl *)hdl;
171 
172 	if (smd) {
173 		if (smd->fd >= 0) {
174 			(void) close(smd->fd);
175 		}
176 		free(smd);
177 	}
178 }
179 
180 int
181 snmp_reinit(picl_snmphdl_t hdl, int clr_linkreset)
182 {
183 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
184 	nvlist_t *nvl;
185 	int i;
186 
187 	(void) mutex_lock(&mibcache_lock);
188 
189 	for (i = 0; i < n_mibcache_rows; i++) {
190 		if ((nvl = mibcache[i]) != NULL)
191 			nvlist_free(nvl);
192 	}
193 
194 	n_mibcache_rows = 0;
195 	if (mibcache) {
196 		free(mibcache);
197 		mibcache = NULL;
198 	}
199 
200 	(void) mutex_unlock(&mibcache_lock);
201 
202 	if (clr_linkreset) {
203 		if (smd == NULL || smd->fd < 0)
204 			return (-1);
205 		else
206 			return (ioctl(smd->fd, DSSNMP_CLRLNKRESET, NULL));
207 	}
208 
209 	return (0);
210 }
211 
212 void
213 snmp_register_group(picl_snmphdl_t hdl, char *oidstrs, int n_oids, int is_vol)
214 {
215 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
216 	oidgroup_t	*oidg;
217 	oidgroup_t	*curr, *prev;
218 	char		*p;
219 	int		i, sz;
220 
221 	/*
222 	 * Allocate a new oidgroup_t
223 	 */
224 	oidg = (oidgroup_t *)calloc(1, sizeof (struct oidgroup));
225 	if (oidg == NULL)
226 		return;
227 
228 	/*
229 	 * Determine how much space is required to register this group
230 	 */
231 	sz = 0;
232 	p = oidstrs;
233 	for (i = 0; i < n_oids; i++) {
234 		sz += strlen(p) + 1;
235 		p = oidstrs + sz;
236 	}
237 
238 	/*
239 	 * Create this oid group
240 	 */
241 	if ((p = (char *)malloc(sz)) == NULL) {
242 		free((void *) oidg);
243 		return;
244 	}
245 
246 	(void) memcpy(p, oidstrs, sz);
247 
248 	oidg->next = NULL;
249 	oidg->oidstrs = p;
250 	oidg->n_oids = n_oids;
251 	oidg->is_volatile = is_vol;
252 
253 	/*
254 	 * Link it to the tail of the list of oid groups
255 	 */
256 	for (prev = NULL, curr = smd->group; curr; curr = curr->next)
257 		prev = curr;
258 
259 	if (prev == NULL)
260 		smd->group = oidg;
261 	else
262 		prev->next = oidg;
263 }
264 
265 /*
266  * snmp_get_int() takes in an OID and returns the integer value
267  * of the object referenced in the passed arg. It returns 0 on
268  * success and -1 on failure.
269  */
270 int
271 snmp_get_int(picl_snmphdl_t hdl, char *prefix, int row, int *val,
272     int *snmp_syserr)
273 {
274 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
275 	oidgroup_t	*grp;
276 	int	ret;
277 	int	err = 0;
278 
279 	if (smd == NULL || prefix == NULL || val == NULL)
280 		return (-1);
281 
282 	/*
283 	 * If this item should not be cached, fetch it directly from
284 	 * the agent using fetch_single_xxx()
285 	 */
286 	if ((grp = locate_oid_group(smd, prefix)) == NULL) {
287 		ret = fetch_single_int(smd, prefix, row, val, &err);
288 
289 		if (snmp_syserr)
290 			*snmp_syserr = err;
291 
292 		return (ret);
293 	}
294 
295 	/*
296 	 * is it in the cache ?
297 	 */
298 	if (lookup_int(prefix, row, val, grp->is_volatile) == 0)
299 		return (0);
300 
301 	/*
302 	 * fetch it from the agent and populate the cache
303 	 */
304 	fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err);
305 	if (snmp_syserr)
306 		*snmp_syserr = err;
307 
308 	/*
309 	 * look it up again and return it
310 	 */
311 	if (lookup_int(prefix, row, val, grp->is_volatile) < 0)
312 		return (-1);
313 
314 	return (0);
315 }
316 
317 /*
318  * snmp_get_str() takes in an OID and returns the string value
319  * of the object referenced in the passed arg. Memory for the string
320  * is allocated within snmp_get_str() and is expected to be freed by
321  * the caller when it is no longer needed. The function returns 0
322  * on success and -1 on failure.
323  */
324 int
325 snmp_get_str(picl_snmphdl_t hdl, char *prefix, int row, char **strp,
326     int *snmp_syserr)
327 {
328 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
329 	oidgroup_t	*grp;
330 	char	*val;
331 	int	ret;
332 	int	err = 0;
333 
334 	if (smd == NULL || prefix == NULL || strp == NULL)
335 		return (-1);
336 
337 	/*
338 	 * Check if this item is cacheable or not. If not, call
339 	 * fetch_single_* to get it directly from the agent
340 	 */
341 	if ((grp = locate_oid_group(smd, prefix)) == NULL) {
342 		ret = fetch_single_str(smd, prefix, row, strp, &err);
343 
344 		if (snmp_syserr)
345 			*snmp_syserr = err;
346 
347 		return (ret);
348 	}
349 
350 	/*
351 	 * See if it's in the cache already
352 	 */
353 	if (lookup_str(prefix, row, &val, grp->is_volatile) == 0) {
354 		if ((*strp = strdup(val)) == NULL)
355 			return (-1);
356 		else
357 			return (0);
358 	}
359 
360 	/*
361 	 * Fetch it from the agent and populate cache
362 	 */
363 	fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err);
364 	if (snmp_syserr)
365 		*snmp_syserr = err;
366 
367 	/*
368 	 * Retry lookup
369 	 */
370 	if (lookup_str(prefix, row, &val, grp->is_volatile) < 0)
371 		return (-1);
372 
373 
374 	if ((*strp = strdup(val)) == NULL)
375 		return (-1);
376 	else
377 		return (0);
378 }
379 
380 /*
381  * snmp_get_bitstr() takes in an OID and returns the bit string value
382  * of the object referenced in the passed args. Memory for the bitstring
383  * is allocated within the function and is expected to be freed by
384  * the caller when it is no longer needed. The function returns 0
385  * on success and -1 on failure.
386  */
387 int
388 snmp_get_bitstr(picl_snmphdl_t hdl, char *prefix, int row, uchar_t **bitstrp,
389     uint_t *nbytes, int *snmp_syserr)
390 {
391 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
392 	oidgroup_t	*grp;
393 	uchar_t	*val;
394 	int	ret;
395 	int	err = 0;
396 
397 	if (smd == NULL || prefix == NULL || bitstrp == NULL || nbytes == NULL)
398 		return (-1);
399 
400 	/*
401 	 * Check if this item is cacheable or not. If not, call
402 	 * fetch_single_* to get it directly from the agent
403 	 */
404 	if ((grp = locate_oid_group(smd, prefix)) == NULL) {
405 		ret = fetch_single_bitstr(smd, prefix, row, bitstrp,
406 		    nbytes, &err);
407 
408 		if (snmp_syserr)
409 			*snmp_syserr = err;
410 
411 		return (ret);
412 	}
413 
414 	/*
415 	 * See if it's in the cache already
416 	 */
417 	if (lookup_bitstr(prefix, row, &val, nbytes, grp->is_volatile) == 0) {
418 		if ((*bitstrp = (uchar_t *)calloc(*nbytes, 1)) == NULL)
419 			return (-1);
420 		(void) memcpy(*bitstrp, (const void *)val, *nbytes);
421 		return (0);
422 	}
423 
424 	/*
425 	 * Fetch it from the agent and populate cache
426 	 */
427 	fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err);
428 	if (snmp_syserr)
429 		*snmp_syserr = err;
430 
431 	/*
432 	 * Retry lookup
433 	 */
434 	if (lookup_bitstr(prefix, row, &val, nbytes, grp->is_volatile) < 0)
435 		return (-1);
436 
437 	if ((*bitstrp = (uchar_t *)calloc(*nbytes, 1)) == NULL)
438 		return (-1);
439 	(void) memcpy(*bitstrp, (const void *)val, *nbytes);
440 
441 	return (0);
442 }
443 
444 /*
445  * snmp_get_nextrow() is similar in operation to SNMP_GETNEXT, but
446  * only just. In particular, this is only expected to return the next
447  * valid row number for the same object, not its value. Since we don't
448  * have any other means, we use this to determine the number of rows
449  * in the table (and the valid ones). This function returns 0 on success
450  * and -1 on failure.
451  */
452 int
453 snmp_get_nextrow(picl_snmphdl_t hdl, char *prefix, int row, int *nextrow,
454     int *snmp_syserr)
455 {
456 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
457 	snmp_pdu_t *reply_pdu;
458 	pdu_varlist_t *vp;
459 	char	*nxt_oidstr;
460 	int	err = 0;
461 
462 	if (smd == NULL || prefix == NULL || nextrow == NULL)
463 		return (-1);
464 
465 	/*
466 	 * The get_nextrow results should *never* go into any cache,
467 	 * since these relationships are dynamically discovered each time.
468 	 */
469 	if ((reply_pdu = fetch_next(smd, prefix, row, &err)) == NULL) {
470 		if (snmp_syserr)
471 			*snmp_syserr = err;
472 
473 		return (-1);
474 	}
475 
476 	/*
477 	 * We are not concerned about the "value" of the lexicographically
478 	 * next object; we only care about the name of that object and
479 	 * its row number (and whether such an object exists or not).
480 	 */
481 	vp = reply_pdu->vars;
482 	if (vp == NULL || vp->name == NULL || vp->type == SNMP_NOSUCHOBJECT ||
483 	    vp->type == SNMP_NOSUCHINSTANCE || vp->type == SNMP_ENDOFMIBVIEW) {
484 		snmp_free_pdu(reply_pdu);
485 		return (-1);
486 	}
487 	if ((nxt_oidstr = oid_to_oidstr(vp->name, vp->name_len - 1)) == NULL) {
488 		snmp_free_pdu(reply_pdu);
489 		return (-1);
490 	}
491 	if (strcmp(nxt_oidstr, prefix) != 0) {
492 		free(nxt_oidstr);
493 		snmp_free_pdu(reply_pdu);
494 		return (-1);
495 	}
496 
497 	/*
498 	 * Ok, so we've got an oid that's simply the next valid row of the
499 	 * passed on object, return this row number.
500 	 */
501 	*nextrow = (vp->name)[vp->name_len-1];
502 
503 	free(nxt_oidstr);
504 	snmp_free_pdu(reply_pdu);
505 
506 	return (0);
507 }
508 
509 /*
510  * Request ids for snmp messages to the agent are sequenced here.
511  */
512 int
513 snmp_get_reqid(void)
514 {
515 	int	ret;
516 
517 	(void) mutex_lock(&snmp_reqid_lock);
518 
519 	ret = snmp_reqid++;
520 
521 	(void) mutex_unlock(&snmp_reqid_lock);
522 
523 	return (ret);
524 }
525 
526 static int
527 lookup_int(char *prefix, int row, int *valp, int is_vol)
528 {
529 	int32_t	*val_arr;
530 	uint_t	nelem;
531 	struct timeval tv;
532 	int	elapsed;
533 
534 	(void) mutex_lock(&mibcache_lock);
535 
536 	if (row >= n_mibcache_rows) {
537 		(void) mutex_unlock(&mibcache_lock);
538 		return (-1);
539 	}
540 
541 	if (mibcache[row] == NULL) {
542 		(void) mutex_unlock(&mibcache_lock);
543 		return (-1);
544 	}
545 
546 	/*
547 	 * If this is a volatile property, we should be searching
548 	 * for an integer-timestamp pair
549 	 */
550 	if (is_vol) {
551 		if (nvlist_lookup_int32_array(mibcache[row], prefix,
552 		    &val_arr, &nelem) != 0) {
553 			(void) mutex_unlock(&mibcache_lock);
554 			return (-1);
555 		}
556 		if (nelem != 2 || val_arr[1] < 0) {
557 			(void) mutex_unlock(&mibcache_lock);
558 			return (-1);
559 		}
560 		if (gettimeofday(&tv, NULL) < 0) {
561 			(void) mutex_unlock(&mibcache_lock);
562 			return (-1);
563 		}
564 		elapsed = tv.tv_sec - val_arr[1];
565 		if (elapsed < 0 || elapsed > MAX_INCACHE_TIME) {
566 			(void) mutex_unlock(&mibcache_lock);
567 			return (-1);
568 		}
569 
570 		*valp = (int)val_arr[0];
571 	} else {
572 		if (nvlist_lookup_int32(mibcache[row], prefix, valp) != 0) {
573 			(void) mutex_unlock(&mibcache_lock);
574 			return (-1);
575 		}
576 	}
577 
578 	(void) mutex_unlock(&mibcache_lock);
579 
580 	return (0);
581 }
582 
583 static int
584 lookup_str(char *prefix, int row, char **valp, int is_vol)
585 {
586 	char	**val_arr;
587 	uint_t	nelem;
588 	struct timeval tv;
589 	int	elapsed;
590 
591 	(void) mutex_lock(&mibcache_lock);
592 
593 	if (row >= n_mibcache_rows) {
594 		(void) mutex_unlock(&mibcache_lock);
595 		return (-1);
596 	}
597 
598 	if (mibcache[row] == NULL) {
599 		(void) mutex_unlock(&mibcache_lock);
600 		return (-1);
601 	}
602 
603 	/*
604 	 * If this is a volatile property, we should be searching
605 	 * for a string-timestamp pair
606 	 */
607 	if (is_vol) {
608 		if (nvlist_lookup_string_array(mibcache[row], prefix,
609 		    &val_arr, &nelem) != 0) {
610 			(void) mutex_unlock(&mibcache_lock);
611 			return (-1);
612 		}
613 		if (nelem != 2 || atoi(val_arr[1]) <= 0) {
614 			(void) mutex_unlock(&mibcache_lock);
615 			return (-1);
616 		}
617 		if (gettimeofday(&tv, NULL) < 0) {
618 			(void) mutex_unlock(&mibcache_lock);
619 			return (-1);
620 		}
621 		elapsed = tv.tv_sec - atoi(val_arr[1]);
622 		if (elapsed < 0 || elapsed > MAX_INCACHE_TIME) {
623 			(void) mutex_unlock(&mibcache_lock);
624 			return (-1);
625 		}
626 
627 		*valp = val_arr[0];
628 	} else {
629 	    if (nvlist_lookup_string(mibcache[row], prefix, valp) != 0) {
630 		    (void) mutex_unlock(&mibcache_lock);
631 		    return (-1);
632 	    }
633 	}
634 
635 	(void) mutex_unlock(&mibcache_lock);
636 
637 	return (0);
638 }
639 
640 static int
641 lookup_bitstr(char *prefix, int row, uchar_t **valp, uint_t *nelem, int is_vol)
642 {
643 	(void) mutex_lock(&mibcache_lock);
644 
645 	if (row >= n_mibcache_rows) {
646 		(void) mutex_unlock(&mibcache_lock);
647 		return (-1);
648 	}
649 
650 	if (mibcache[row] == NULL) {
651 		(void) mutex_unlock(&mibcache_lock);
652 		return (-1);
653 	}
654 
655 	/*
656 	 * We don't support volatile bit string values yet. The nvlist
657 	 * functions don't support bitstring arrays like they do charstring
658 	 * arrays, so we would need to do things in a convoluted way,
659 	 * probably by attaching the timestamp as part of the byte array
660 	 * itself. However, the need for volatile bitstrings isn't there
661 	 * yet, to justify the effort.
662 	 */
663 	if (is_vol) {
664 		(void) mutex_unlock(&mibcache_lock);
665 		return (-1);
666 	}
667 
668 	if (nvlist_lookup_byte_array(mibcache[row], prefix, valp, nelem) != 0) {
669 		(void) mutex_unlock(&mibcache_lock);
670 		return (-1);
671 	}
672 
673 	(void) mutex_unlock(&mibcache_lock);
674 
675 	return (0);
676 }
677 
678 static int
679 search_oid_in_group(char *prefix, char *oidstrs, int n_oids)
680 {
681 	char	*p;
682 	int	i;
683 
684 	p = oidstrs;
685 	for (i = 0; i < n_oids; i++) {
686 		if (strcmp(p, prefix) == 0)
687 			return (0);
688 
689 		p += strlen(p) + 1;
690 	}
691 
692 	return (-1);
693 }
694 
695 static oidgroup_t *
696 locate_oid_group(struct picl_snmphdl *smd, char *prefix)
697 {
698 	oidgroup_t	*grp;
699 
700 	if (smd == NULL)
701 		return (NULL);
702 
703 	if (smd->group == NULL)
704 		return (NULL);
705 
706 	for (grp = smd->group; grp; grp = grp->next) {
707 		if (search_oid_in_group(prefix, grp->oidstrs,
708 		    grp->n_oids) == 0) {
709 			return (grp);
710 		}
711 	}
712 
713 	return (NULL);
714 }
715 
716 static int
717 fetch_single_int(struct picl_snmphdl *smd, char *prefix, int row, int *ival,
718     int *snmp_syserr)
719 {
720 	snmp_pdu_t *reply_pdu;
721 	pdu_varlist_t *vp;
722 
723 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
724 		return (-1);
725 
726 	/*
727 	 * Note that we don't make any distinction between unsigned int
728 	 * value and signed int value at this point, since we provide
729 	 * only snmp_get_int() at the higher level. While it is possible
730 	 * to provide an entirely separate interface such as snmp_get_uint(),
731 	 * that's quite unnecessary, because we don't do any interpretation
732 	 * of the received value. Besides, the sizes of int and uint are
733 	 * the same and the sizes of all pointers are the same (so val.iptr
734 	 * would be the same as val.uiptr in pdu_varlist_t). If/when we
735 	 * violate any of these assumptions, it will be time to add
736 	 * snmp_get_uint().
737 	 */
738 	vp = reply_pdu->vars;
739 	if (vp == NULL || vp->val.iptr == NULL) {
740 		snmp_free_pdu(reply_pdu);
741 		return (-1);
742 	}
743 
744 	*ival = *(vp->val.iptr);
745 
746 	snmp_free_pdu(reply_pdu);
747 
748 	return (0);
749 }
750 
751 static int
752 fetch_single_str(struct picl_snmphdl *smd, char *prefix, int row, char **valp,
753     int *snmp_syserr)
754 {
755 	snmp_pdu_t *reply_pdu;
756 	pdu_varlist_t *vp;
757 
758 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
759 		return (-1);
760 
761 	vp = reply_pdu->vars;
762 	if (vp == NULL || vp->val.str == NULL) {
763 		snmp_free_pdu(reply_pdu);
764 		return (-1);
765 	}
766 
767 	*valp = strdup((const char *)(vp->val.str));
768 
769 	snmp_free_pdu(reply_pdu);
770 
771 	return (0);
772 }
773 
774 static int
775 fetch_single_bitstr(struct picl_snmphdl *smd, char *prefix, int row,
776     uchar_t **valp, uint_t *nelem, int *snmp_syserr)
777 {
778 	snmp_pdu_t *reply_pdu;
779 	pdu_varlist_t *vp;
780 
781 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
782 		return (-1);
783 
784 	vp = reply_pdu->vars;
785 	if (vp == NULL || vp->val.str == NULL) {
786 		snmp_free_pdu(reply_pdu);
787 		return (-1);
788 	}
789 
790 	if ((*valp = (uchar_t *)calloc(vp->val_len, 1)) == NULL) {
791 		snmp_free_pdu(reply_pdu);
792 		return (-1);
793 	}
794 
795 	*nelem = vp->val_len;
796 	(void) memcpy(*valp, (const void *)(vp->val.str),
797 	    (size_t)(vp->val_len));
798 
799 	snmp_free_pdu(reply_pdu);
800 
801 	return (0);
802 }
803 
804 static snmp_pdu_t *
805 fetch_single(struct picl_snmphdl *smd, char *prefix, int row, int *snmp_syserr)
806 {
807 	snmp_pdu_t	*pdu, *reply_pdu;
808 
809 	LOGGET(TAG_CMD_REQUEST, prefix, row);
810 
811 	if ((pdu = snmp_create_pdu(SNMP_MSG_GET, 0, prefix, 1, row)) == NULL)
812 		return (NULL);
813 
814 	LOGPDU(TAG_REQUEST_PDU, pdu);
815 
816 	if (snmp_make_packet(pdu) < 0) {
817 		snmp_free_pdu(pdu);
818 		return (NULL);
819 	}
820 
821 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
822 
823 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
824 		snmp_free_pdu(pdu);
825 		return (NULL);
826 	}
827 
828 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
829 		snmp_free_pdu(pdu);
830 		return (NULL);
831 	}
832 
833 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
834 
835 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
836 	    pdu->reply_pktsz);
837 
838 	LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
839 
840 	snmp_free_pdu(pdu);
841 
842 	return (reply_pdu);
843 }
844 
845 static void
846 fetch_bulk(struct picl_snmphdl *smd, char *oidstrs, int n_oids,
847     int row, int is_vol, int *snmp_syserr)
848 {
849 	snmp_pdu_t	*pdu, *reply_pdu;
850 	int		max_reps;
851 
852 	LOGBULK(TAG_CMD_REQUEST, n_oids, oidstrs, row);
853 
854 	/*
855 	 * If we're fetching volatile properties using BULKGET, don't
856 	 * venture to get multiple rows (passing max_reps=0 will make
857 	 * snmp_create_pdu() fetch SNMP_DEF_MAX_REPETITIONS rows)
858 	 */
859 	max_reps = is_vol ? 1 : 0;
860 
861 	pdu = snmp_create_pdu(SNMP_MSG_GETBULK, max_reps, oidstrs, n_oids, row);
862 	if (pdu == NULL)
863 		return;
864 
865 	LOGPDU(TAG_REQUEST_PDU, pdu);
866 
867 	/*
868 	 * Make an ASN.1 encoded packet from the PDU information
869 	 */
870 	if (snmp_make_packet(pdu) < 0) {
871 		snmp_free_pdu(pdu);
872 		return;
873 	}
874 
875 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
876 
877 	/*
878 	 * Send the request packet to the agent
879 	 */
880 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
881 		snmp_free_pdu(pdu);
882 		return;
883 	}
884 
885 	/*
886 	 * Receive response from the agent into the reply packet buffer
887 	 * in the request PDU
888 	 */
889 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
890 		snmp_free_pdu(pdu);
891 		return;
892 	}
893 
894 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
895 
896 	/*
897 	 * Parse the reply, validate the response and create a
898 	 * reply-PDU out of the information. Populate the mibcache
899 	 * with the received values.
900 	 */
901 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
902 	    pdu->reply_pktsz);
903 	if (reply_pdu) {
904 		LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
905 
906 		if (reply_pdu->errstat == SNMP_ERR_NOERROR)
907 			mibcache_populate(reply_pdu, is_vol);
908 
909 		snmp_free_pdu(reply_pdu);
910 	}
911 
912 	snmp_free_pdu(pdu);
913 }
914 
915 static snmp_pdu_t *
916 fetch_next(struct picl_snmphdl *smd, char *prefix, int row, int *snmp_syserr)
917 {
918 	snmp_pdu_t	*pdu, *reply_pdu;
919 
920 	LOGNEXT(TAG_CMD_REQUEST, prefix, row);
921 
922 	pdu = snmp_create_pdu(SNMP_MSG_GETNEXT, 0, prefix, 1, row);
923 	if (pdu == NULL)
924 		return (NULL);
925 
926 	LOGPDU(TAG_REQUEST_PDU, pdu);
927 
928 	if (snmp_make_packet(pdu) < 0) {
929 		snmp_free_pdu(pdu);
930 		return (NULL);
931 	}
932 
933 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
934 
935 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
936 		snmp_free_pdu(pdu);
937 		return (NULL);
938 	}
939 
940 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
941 		snmp_free_pdu(pdu);
942 		return (NULL);
943 	}
944 
945 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
946 
947 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
948 	    pdu->reply_pktsz);
949 
950 	LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
951 
952 	snmp_free_pdu(pdu);
953 
954 	return (reply_pdu);
955 }
956 
957 static int
958 snmp_send_request(struct picl_snmphdl *smd, snmp_pdu_t *pdu, int *snmp_syserr)
959 {
960 	extern int	errno;
961 #ifdef USE_SOCKETS
962 	int		ret;
963 #endif
964 
965 	if (smd->fd < 0)
966 		return (-1);
967 
968 	if (pdu == NULL || pdu->req_pkt == NULL)
969 		return (-1);
970 
971 #ifdef USE_SOCKETS
972 	ret = -1;
973 	while (ret < 0) {
974 		LOGIO(TAG_SENDTO, smd->fd, pdu->req_pkt, pdu->req_pktsz);
975 
976 		ret = sendto(smd->fd, pdu->req_pkt, pdu->req_pktsz, 0,
977 		    (struct sockaddr *)&smd->agent_addr,
978 		    sizeof (struct sockaddr));
979 		if (ret < 0 && errno != EINTR) {
980 			return (-1);
981 		}
982 	}
983 #else
984 	LOGIO(TAG_WRITE, smd->fd, pdu->req_pkt, pdu->req_pktsz);
985 
986 	if (write(smd->fd, pdu->req_pkt, pdu->req_pktsz) < 0) {
987 		if (snmp_syserr)
988 			*snmp_syserr = errno;
989 		return (-1);
990 	}
991 #endif
992 
993 #ifdef SNMP_DEBUG
994 	snmp_nsends++;
995 	snmp_sentbytes += pdu->req_pktsz;
996 #endif
997 
998 	return (0);
999 }
1000 
1001 static int
1002 snmp_recv_reply(struct picl_snmphdl *smd, snmp_pdu_t *pdu, int *snmp_syserr)
1003 {
1004 	struct dssnmp_info	snmp_info;
1005 	size_t	pktsz;
1006 	uchar_t	*pkt;
1007 	extern int errno;
1008 #ifdef USE_SOCKETS
1009 	struct sockaddr_in 	from;
1010 	int	fromlen;
1011 	ssize_t	msgsz;
1012 #endif
1013 
1014 	if (smd->fd < 0 || pdu == NULL)
1015 		return (-1);
1016 
1017 #ifdef USE_SOCKETS
1018 	if ((pkt = (uchar_t *)calloc(1, SNMP_MAX_RECV_PKTSZ)) == NULL)
1019 		return (-1);
1020 
1021 	fromlen = sizeof (struct sockaddr_in);
1022 
1023 	LOGIO(TAG_RECVFROM, smd->fd, pkt, SNMP_MAX_RECV_PKTSZ);
1024 
1025 	msgsz = recvfrom(smd->fd, pkt, SNMP_MAX_RECV_PKTSZ, 0,
1026 	    (struct sockaddr *)&from, &fromlen);
1027 	if (msgsz  < 0 || msgsz >= SNMP_MAX_RECV_PKTSZ) {
1028 		free(pkt);
1029 		return (-1);
1030 	}
1031 
1032 	pktsz = (size_t)msgsz;
1033 #else
1034 	LOGIO(TAG_IOCTL, smd->fd, DSSNMP_GETINFO, &snmp_info);
1035 
1036 	/*
1037 	 * The ioctl will block until we have snmp data available
1038 	 */
1039 	if (ioctl(smd->fd, DSSNMP_GETINFO, &snmp_info) < 0) {
1040 		if (snmp_syserr)
1041 			*snmp_syserr = errno;
1042 		return (-1);
1043 	}
1044 
1045 	pktsz = snmp_info.size;
1046 	if ((pkt = (uchar_t *)calloc(1, pktsz)) == NULL)
1047 		return (-1);
1048 
1049 	LOGIO(TAG_READ, smd->fd, pkt, pktsz);
1050 
1051 	if (read(smd->fd, pkt, pktsz) < 0) {
1052 		free(pkt);
1053 		if (snmp_syserr)
1054 			*snmp_syserr = errno;
1055 		return (-1);
1056 	}
1057 #endif
1058 
1059 	pdu->reply_pkt = pkt;
1060 	pdu->reply_pktsz = pktsz;
1061 
1062 #ifdef SNMP_DEBUG
1063 	snmp_nrecvs++;
1064 	snmp_rcvdbytes += pktsz;
1065 #endif
1066 
1067 	return (0);
1068 }
1069 
1070 static int
1071 mibcache_realloc(int hint)
1072 {
1073 	uint_t		count = (uint_t)hint;
1074 	nvlist_t	**p;
1075 
1076 	if (hint < 0)
1077 		return (-1);
1078 
1079 	(void) mutex_lock(&mibcache_lock);
1080 
1081 	if (hint < n_mibcache_rows) {
1082 		(void) mutex_unlock(&mibcache_lock);
1083 		return (0);
1084 	}
1085 
1086 	count =  ((count >> MIBCACHE_BLK_SHIFT) + 1) << MIBCACHE_BLK_SHIFT;
1087 
1088 	p = (nvlist_t **)calloc(count, sizeof (nvlist_t *));
1089 	if (p == NULL) {
1090 		(void) mutex_unlock(&mibcache_lock);
1091 		return (-1);
1092 	}
1093 
1094 	if (mibcache) {
1095 		(void) memcpy((void *) p, (void *) mibcache,
1096 		    n_mibcache_rows * sizeof (nvlist_t *));
1097 		free((void *) mibcache);
1098 	}
1099 
1100 	mibcache = p;
1101 	n_mibcache_rows = count;
1102 
1103 	(void) mutex_unlock(&mibcache_lock);
1104 
1105 	return (0);
1106 }
1107 
1108 
1109 /*
1110  * Scan each variable in the returned PDU's bindings and populate
1111  * the cache appropriately
1112  */
1113 static void
1114 mibcache_populate(snmp_pdu_t *pdu, int is_vol)
1115 {
1116 	pdu_varlist_t	*vp;
1117 	int		row, ret;
1118 	char		*oidstr;
1119 	struct timeval	tv;
1120 	int		tod;	/* in secs */
1121 	char		tod_str[MAX_INT_LEN];
1122 	int		ival_arr[2];
1123 	char		*sval_arr[2];
1124 
1125 	/*
1126 	 * If we're populating volatile properties, we also store a
1127 	 * timestamp with each property value. When we lookup, we
1128 	 * check the current time against this timestamp to determine
1129 	 * if we need to refetch the value or not (refetch if it has
1130 	 * been in for far too long).
1131 	 */
1132 	if (is_vol) {
1133 		if (gettimeofday(&tv, NULL) < 0)
1134 			tod = -1;
1135 		else
1136 			tod = (int)tv.tv_sec;
1137 
1138 		tod_str[0] = 0;
1139 		(void) snprintf(tod_str, MAX_INT_LEN, "%d", tod);
1140 
1141 		ival_arr[1] = tod;
1142 		sval_arr[1] = (char *)tod_str;
1143 	}
1144 
1145 	for (vp = pdu->vars; vp; vp = vp->nextvar) {
1146 		if (vp->type != ASN_INTEGER && vp->type != ASN_OCTET_STR &&
1147 		    vp->type != ASN_BIT_STR) {
1148 			continue;
1149 		}
1150 
1151 		if (vp->name == NULL || vp->val.str == NULL)
1152 			continue;
1153 
1154 		row = (vp->name)[vp->name_len-1];
1155 
1156 		(void) mutex_lock(&mibcache_lock);
1157 
1158 		if (row >= n_mibcache_rows) {
1159 			(void) mutex_unlock(&mibcache_lock);
1160 			if (mibcache_realloc(row) < 0)
1161 				continue;
1162 			(void) mutex_lock(&mibcache_lock);
1163 		}
1164 		ret = 0;
1165 		if (mibcache[row] == NULL)
1166 			ret = nvlist_alloc(&mibcache[row], NV_UNIQUE_NAME, 0);
1167 
1168 		(void) mutex_unlock(&mibcache_lock);
1169 
1170 		if (ret != 0)
1171 			continue;
1172 
1173 		/*
1174 		 * Convert the standard OID form into an oid string that
1175 		 * we can use as the key to lookup. Since we only search
1176 		 * by the prefix (mibcache is really an array of nvlist_t
1177 		 * pointers), ignore the leaf subid.
1178 		 */
1179 		oidstr = oid_to_oidstr(vp->name, vp->name_len - 1);
1180 		if (oidstr == NULL)
1181 			continue;
1182 
1183 		(void) mutex_lock(&mibcache_lock);
1184 
1185 		if (vp->type == ASN_INTEGER) {
1186 			if (is_vol) {
1187 				ival_arr[0] = *(vp->val.iptr);
1188 				(void) nvlist_add_int32_array(mibcache[row],
1189 				    oidstr, ival_arr, 2);
1190 			} else {
1191 				nvlist_add_int32(mibcache[row],
1192 				    oidstr, *(vp->val.iptr));
1193 			}
1194 
1195 		} else if (vp->type == ASN_OCTET_STR) {
1196 			if (is_vol) {
1197 				sval_arr[0] = (char *)vp->val.str;
1198 				(void) nvlist_add_string_array(mibcache[row],
1199 				    oidstr, sval_arr, 2);
1200 			} else {
1201 				(void) nvlist_add_string(mibcache[row],
1202 				    oidstr, (const char *)(vp->val.str));
1203 			}
1204 		} else if (vp->type == ASN_BIT_STR) {
1205 			/*
1206 			 * We don't support yet bit string objects that are
1207 			 * volatile values.
1208 			 */
1209 			if (!is_vol) {
1210 				(void) nvlist_add_byte_array(mibcache[row],
1211 				    oidstr, (uchar_t *)(vp->val.str),
1212 				    (uint_t)vp->val_len);
1213 			}
1214 		}
1215 		(void) mutex_unlock(&mibcache_lock);
1216 
1217 		free(oidstr);
1218 	}
1219 }
1220 
1221 static char *
1222 oid_to_oidstr(oid *objid, size_t n_subids)
1223 {
1224 	char	*oidstr;
1225 	char	subid_str[MAX_INT_LEN];
1226 	int	i, isize;
1227 	size_t	oidstr_sz;
1228 
1229 	/*
1230 	 * ugly, but for now this will have to do.
1231 	 */
1232 	oidstr_sz = sizeof (subid_str) * n_subids;
1233 	oidstr = calloc(1, oidstr_sz);
1234 
1235 	for (i = 0; i < n_subids; i++) {
1236 		(void) memset(subid_str, 0, sizeof (subid_str));
1237 		isize = snprintf(subid_str, sizeof (subid_str), "%d",
1238 			objid[i]);
1239 		if (isize >= sizeof (subid_str))
1240 			return (NULL);
1241 
1242 		(void) strlcat(oidstr, subid_str, oidstr_sz);
1243 		if (i < (n_subids - 1))
1244 			(void) strlcat(oidstr, ".", oidstr_sz);
1245 	}
1246 
1247 	return (oidstr);
1248 }
1249