xref: /illumos-gate/usr/src/cmd/picl/plugins/sun4v/lib/snmp/snmplib.c (revision ca9327a6de44d69ddab3668cc1e143ce781387a3)
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 2008 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 	*strp = NULL;
338 	/*
339 	 * Check if this item is cacheable or not. If not, call
340 	 * fetch_single_* to get it directly from the agent
341 	 */
342 	if ((grp = locate_oid_group(smd, prefix)) == NULL) {
343 		ret = fetch_single_str(smd, prefix, row, strp, &err);
344 
345 		if (snmp_syserr)
346 			*snmp_syserr = err;
347 
348 		return (ret);
349 	}
350 
351 	/*
352 	 * See if it's in the cache already
353 	 */
354 	if (lookup_str(prefix, row, &val, grp->is_volatile) == 0) {
355 		if ((*strp = strdup(val)) == NULL)
356 			return (-1);
357 		else
358 			return (0);
359 	}
360 
361 	/*
362 	 * Fetch it from the agent and populate cache
363 	 */
364 	fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err);
365 	if (snmp_syserr)
366 		*snmp_syserr = err;
367 
368 	/*
369 	 * Retry lookup
370 	 */
371 	if (lookup_str(prefix, row, &val, grp->is_volatile) < 0)
372 		return (-1);
373 
374 
375 	if ((*strp = strdup(val)) == NULL)
376 		return (-1);
377 	else
378 		return (0);
379 }
380 
381 /*
382  * snmp_get_bitstr() takes in an OID and returns the bit string value
383  * of the object referenced in the passed args. Memory for the bitstring
384  * is allocated within the function and is expected to be freed by
385  * the caller when it is no longer needed. The function returns 0
386  * on success and -1 on failure.
387  */
388 int
389 snmp_get_bitstr(picl_snmphdl_t hdl, char *prefix, int row, uchar_t **bitstrp,
390     uint_t *nbytes, int *snmp_syserr)
391 {
392 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
393 	oidgroup_t	*grp;
394 	uchar_t	*val;
395 	int	ret;
396 	int	err = 0;
397 
398 	if (smd == NULL || prefix == NULL || bitstrp == NULL || nbytes == NULL)
399 		return (-1);
400 
401 	*bitstrp = NULL;
402 	/*
403 	 * Check if this item is cacheable or not. If not, call
404 	 * fetch_single_* to get it directly from the agent
405 	 */
406 	if ((grp = locate_oid_group(smd, prefix)) == NULL) {
407 		ret = fetch_single_bitstr(smd, prefix, row, bitstrp,
408 		    nbytes, &err);
409 
410 		if (snmp_syserr)
411 			*snmp_syserr = err;
412 
413 		return (ret);
414 	}
415 
416 	/*
417 	 * See if it's in the cache already
418 	 */
419 	if (lookup_bitstr(prefix, row, &val, nbytes, grp->is_volatile) == 0) {
420 		if ((*bitstrp = (uchar_t *)calloc(*nbytes, 1)) == NULL)
421 			return (-1);
422 		(void) memcpy(*bitstrp, (const void *)val, *nbytes);
423 		return (0);
424 	}
425 
426 	/*
427 	 * Fetch it from the agent and populate cache
428 	 */
429 	fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err);
430 	if (snmp_syserr)
431 		*snmp_syserr = err;
432 
433 	/*
434 	 * Retry lookup
435 	 */
436 	if (lookup_bitstr(prefix, row, &val, nbytes, grp->is_volatile) < 0)
437 		return (-1);
438 
439 	if ((*bitstrp = (uchar_t *)calloc(*nbytes, 1)) == NULL)
440 		return (-1);
441 	(void) memcpy(*bitstrp, (const void *)val, *nbytes);
442 
443 	return (0);
444 }
445 
446 /*
447  * snmp_get_nextrow() is similar in operation to SNMP_GETNEXT, but
448  * only just. In particular, this is only expected to return the next
449  * valid row number for the same object, not its value. Since we don't
450  * have any other means, we use this to determine the number of rows
451  * in the table (and the valid ones). This function returns 0 on success
452  * and -1 on failure.
453  */
454 int
455 snmp_get_nextrow(picl_snmphdl_t hdl, char *prefix, int row, int *nextrow,
456     int *snmp_syserr)
457 {
458 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
459 	snmp_pdu_t *reply_pdu;
460 	pdu_varlist_t *vp;
461 	char	*nxt_oidstr;
462 	int	err = 0;
463 
464 	if (smd == NULL || prefix == NULL || nextrow == NULL) {
465 		if (snmp_syserr)
466 			*snmp_syserr = EINVAL;
467 		return (-1);
468 	}
469 
470 	/*
471 	 * The get_nextrow results should *never* go into any cache,
472 	 * since these relationships are dynamically discovered each time.
473 	 */
474 	if ((reply_pdu = fetch_next(smd, prefix, row, &err)) == NULL) {
475 		if (snmp_syserr)
476 			*snmp_syserr = err;
477 		return (-1);
478 	}
479 
480 	/*
481 	 * We are not concerned about the "value" of the lexicographically
482 	 * next object; we only care about the name of that object and
483 	 * its row number (and whether such an object exists or not).
484 	 */
485 	vp = reply_pdu->vars;
486 
487 	/*
488 	 * This indicates that we're at the end of the MIB view.
489 	 */
490 	if (vp == NULL || vp->name == NULL || vp->type == SNMP_NOSUCHOBJECT ||
491 	    vp->type == SNMP_NOSUCHINSTANCE || vp->type == SNMP_ENDOFMIBVIEW) {
492 		snmp_free_pdu(reply_pdu);
493 		if (snmp_syserr)
494 			*snmp_syserr = ENOSPC;
495 		return (-1);
496 	}
497 
498 	/*
499 	 * need to be able to convert the OID
500 	 */
501 	if ((nxt_oidstr = oid_to_oidstr(vp->name, vp->name_len - 1)) == NULL) {
502 		snmp_free_pdu(reply_pdu);
503 		if (snmp_syserr)
504 			*snmp_syserr = ENOMEM;
505 		return (-1);
506 	}
507 
508 	/*
509 	 * We're on to the next table.
510 	 */
511 	if (strcmp(nxt_oidstr, prefix) != 0) {
512 		free(nxt_oidstr);
513 		snmp_free_pdu(reply_pdu);
514 		if (snmp_syserr)
515 			*snmp_syserr = ENOENT;
516 		return (-1);
517 	}
518 
519 	/*
520 	 * Ok, so we've got an oid that's simply the next valid row of the
521 	 * passed on object, return this row number.
522 	 */
523 	*nextrow = (vp->name)[vp->name_len-1];
524 
525 	free(nxt_oidstr);
526 	snmp_free_pdu(reply_pdu);
527 
528 	return (0);
529 }
530 
531 /*
532  * Request ids for snmp messages to the agent are sequenced here.
533  */
534 int
535 snmp_get_reqid(void)
536 {
537 	int	ret;
538 
539 	(void) mutex_lock(&snmp_reqid_lock);
540 
541 	ret = snmp_reqid++;
542 
543 	(void) mutex_unlock(&snmp_reqid_lock);
544 
545 	return (ret);
546 }
547 
548 static int
549 lookup_int(char *prefix, int row, int *valp, int is_vol)
550 {
551 	int32_t	*val_arr;
552 	uint_t	nelem;
553 	struct timeval tv;
554 	int	elapsed;
555 
556 	(void) mutex_lock(&mibcache_lock);
557 
558 	if (row >= n_mibcache_rows) {
559 		(void) mutex_unlock(&mibcache_lock);
560 		return (-1);
561 	}
562 
563 	if (mibcache[row] == NULL) {
564 		(void) mutex_unlock(&mibcache_lock);
565 		return (-1);
566 	}
567 
568 	/*
569 	 * If this is a volatile property, we should be searching
570 	 * for an integer-timestamp pair
571 	 */
572 	if (is_vol) {
573 		if (nvlist_lookup_int32_array(mibcache[row], prefix,
574 		    &val_arr, &nelem) != 0) {
575 			(void) mutex_unlock(&mibcache_lock);
576 			return (-1);
577 		}
578 		if (nelem != 2 || val_arr[1] < 0) {
579 			(void) mutex_unlock(&mibcache_lock);
580 			return (-1);
581 		}
582 		if (gettimeofday(&tv, NULL) < 0) {
583 			(void) mutex_unlock(&mibcache_lock);
584 			return (-1);
585 		}
586 		elapsed = tv.tv_sec - val_arr[1];
587 		if (elapsed < 0 || elapsed > MAX_INCACHE_TIME) {
588 			(void) mutex_unlock(&mibcache_lock);
589 			return (-1);
590 		}
591 
592 		*valp = (int)val_arr[0];
593 	} else {
594 		if (nvlist_lookup_int32(mibcache[row], prefix, valp) != 0) {
595 			(void) mutex_unlock(&mibcache_lock);
596 			return (-1);
597 		}
598 	}
599 
600 	(void) mutex_unlock(&mibcache_lock);
601 
602 	return (0);
603 }
604 
605 static int
606 lookup_str(char *prefix, int row, char **valp, int is_vol)
607 {
608 	char	**val_arr;
609 	uint_t	nelem;
610 	struct timeval tv;
611 	int	elapsed;
612 
613 	(void) mutex_lock(&mibcache_lock);
614 
615 	if (row >= n_mibcache_rows) {
616 		(void) mutex_unlock(&mibcache_lock);
617 		return (-1);
618 	}
619 
620 	if (mibcache[row] == NULL) {
621 		(void) mutex_unlock(&mibcache_lock);
622 		return (-1);
623 	}
624 
625 	/*
626 	 * If this is a volatile property, we should be searching
627 	 * for a string-timestamp pair
628 	 */
629 	if (is_vol) {
630 		if (nvlist_lookup_string_array(mibcache[row], prefix,
631 		    &val_arr, &nelem) != 0) {
632 			(void) mutex_unlock(&mibcache_lock);
633 			return (-1);
634 		}
635 		if (nelem != 2 || atoi(val_arr[1]) <= 0) {
636 			(void) mutex_unlock(&mibcache_lock);
637 			return (-1);
638 		}
639 		if (gettimeofday(&tv, NULL) < 0) {
640 			(void) mutex_unlock(&mibcache_lock);
641 			return (-1);
642 		}
643 		elapsed = tv.tv_sec - atoi(val_arr[1]);
644 		if (elapsed < 0 || elapsed > MAX_INCACHE_TIME) {
645 			(void) mutex_unlock(&mibcache_lock);
646 			return (-1);
647 		}
648 
649 		*valp = val_arr[0];
650 	} else {
651 		if (nvlist_lookup_string(mibcache[row], prefix, valp) != 0) {
652 			(void) mutex_unlock(&mibcache_lock);
653 			return (-1);
654 		}
655 	}
656 
657 	(void) mutex_unlock(&mibcache_lock);
658 
659 	return (0);
660 }
661 
662 static int
663 lookup_bitstr(char *prefix, int row, uchar_t **valp, uint_t *nelem, int is_vol)
664 {
665 	(void) mutex_lock(&mibcache_lock);
666 
667 	if (row >= n_mibcache_rows) {
668 		(void) mutex_unlock(&mibcache_lock);
669 		return (-1);
670 	}
671 
672 	if (mibcache[row] == NULL) {
673 		(void) mutex_unlock(&mibcache_lock);
674 		return (-1);
675 	}
676 
677 	/*
678 	 * We don't support volatile bit string values yet. The nvlist
679 	 * functions don't support bitstring arrays like they do charstring
680 	 * arrays, so we would need to do things in a convoluted way,
681 	 * probably by attaching the timestamp as part of the byte array
682 	 * itself. However, the need for volatile bitstrings isn't there
683 	 * yet, to justify the effort.
684 	 */
685 	if (is_vol) {
686 		(void) mutex_unlock(&mibcache_lock);
687 		return (-1);
688 	}
689 
690 	if (nvlist_lookup_byte_array(mibcache[row], prefix, valp, nelem) != 0) {
691 		(void) mutex_unlock(&mibcache_lock);
692 		return (-1);
693 	}
694 
695 	(void) mutex_unlock(&mibcache_lock);
696 
697 	return (0);
698 }
699 
700 static int
701 search_oid_in_group(char *prefix, char *oidstrs, int n_oids)
702 {
703 	char	*p;
704 	int	i;
705 
706 	p = oidstrs;
707 	for (i = 0; i < n_oids; i++) {
708 		if (strcmp(p, prefix) == 0)
709 			return (0);
710 
711 		p += strlen(p) + 1;
712 	}
713 
714 	return (-1);
715 }
716 
717 static oidgroup_t *
718 locate_oid_group(struct picl_snmphdl *smd, char *prefix)
719 {
720 	oidgroup_t	*grp;
721 
722 	if (smd == NULL)
723 		return (NULL);
724 
725 	if (smd->group == NULL)
726 		return (NULL);
727 
728 	for (grp = smd->group; grp; grp = grp->next) {
729 		if (search_oid_in_group(prefix, grp->oidstrs,
730 		    grp->n_oids) == 0) {
731 			return (grp);
732 		}
733 	}
734 
735 	return (NULL);
736 }
737 
738 static int
739 fetch_single_int(struct picl_snmphdl *smd, char *prefix, int row, int *ival,
740     int *snmp_syserr)
741 {
742 	snmp_pdu_t *reply_pdu;
743 	pdu_varlist_t *vp;
744 
745 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
746 		return (-1);
747 
748 	/*
749 	 * Note that we don't make any distinction between unsigned int
750 	 * value and signed int value at this point, since we provide
751 	 * only snmp_get_int() at the higher level. While it is possible
752 	 * to provide an entirely separate interface such as snmp_get_uint(),
753 	 * that's quite unnecessary, because we don't do any interpretation
754 	 * of the received value. Besides, the sizes of int and uint are
755 	 * the same and the sizes of all pointers are the same (so val.iptr
756 	 * would be the same as val.uiptr in pdu_varlist_t). If/when we
757 	 * violate any of these assumptions, it will be time to add
758 	 * snmp_get_uint().
759 	 */
760 	vp = reply_pdu->vars;
761 	if (vp == NULL || vp->val.iptr == NULL) {
762 		snmp_free_pdu(reply_pdu);
763 		return (-1);
764 	}
765 
766 	*ival = *(vp->val.iptr);
767 
768 	snmp_free_pdu(reply_pdu);
769 
770 	return (0);
771 }
772 
773 static int
774 fetch_single_str(struct picl_snmphdl *smd, char *prefix, int row, char **valp,
775     int *snmp_syserr)
776 {
777 	snmp_pdu_t *reply_pdu;
778 	pdu_varlist_t *vp;
779 
780 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
781 		return (-1);
782 
783 	vp = reply_pdu->vars;
784 	if (vp == NULL || vp->val.str == NULL) {
785 		snmp_free_pdu(reply_pdu);
786 		return (-1);
787 	}
788 
789 	*valp = strdup((const char *)(vp->val.str));
790 
791 	snmp_free_pdu(reply_pdu);
792 
793 	return (0);
794 }
795 
796 static int
797 fetch_single_bitstr(struct picl_snmphdl *smd, char *prefix, int row,
798     uchar_t **valp, uint_t *nelem, int *snmp_syserr)
799 {
800 	snmp_pdu_t *reply_pdu;
801 	pdu_varlist_t *vp;
802 
803 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
804 		return (-1);
805 
806 	vp = reply_pdu->vars;
807 	if (vp == NULL || vp->val.str == NULL) {
808 		snmp_free_pdu(reply_pdu);
809 		return (-1);
810 	}
811 
812 	if ((*valp = (uchar_t *)calloc(vp->val_len, 1)) == NULL) {
813 		snmp_free_pdu(reply_pdu);
814 		return (-1);
815 	}
816 
817 	*nelem = vp->val_len;
818 	(void) memcpy(*valp, (const void *)(vp->val.str),
819 	    (size_t)(vp->val_len));
820 
821 	snmp_free_pdu(reply_pdu);
822 
823 	return (0);
824 }
825 
826 static snmp_pdu_t *
827 fetch_single(struct picl_snmphdl *smd, char *prefix, int row, int *snmp_syserr)
828 {
829 	snmp_pdu_t	*pdu, *reply_pdu;
830 
831 	LOGGET(TAG_CMD_REQUEST, prefix, row);
832 
833 	if ((pdu = snmp_create_pdu(SNMP_MSG_GET, 0, prefix, 1, row)) == NULL)
834 		return (NULL);
835 
836 	LOGPDU(TAG_REQUEST_PDU, pdu);
837 
838 	if (snmp_make_packet(pdu) < 0) {
839 		snmp_free_pdu(pdu);
840 		return (NULL);
841 	}
842 
843 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
844 
845 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
846 		snmp_free_pdu(pdu);
847 		return (NULL);
848 	}
849 
850 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
851 		snmp_free_pdu(pdu);
852 		return (NULL);
853 	}
854 
855 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
856 
857 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
858 	    pdu->reply_pktsz);
859 
860 	LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
861 
862 	snmp_free_pdu(pdu);
863 
864 	return (reply_pdu);
865 }
866 
867 static void
868 fetch_bulk(struct picl_snmphdl *smd, char *oidstrs, int n_oids,
869     int row, int is_vol, int *snmp_syserr)
870 {
871 	snmp_pdu_t	*pdu, *reply_pdu;
872 	int		max_reps;
873 
874 	LOGBULK(TAG_CMD_REQUEST, n_oids, oidstrs, row);
875 
876 	/*
877 	 * If we're fetching volatile properties using BULKGET, don't
878 	 * venture to get multiple rows (passing max_reps=0 will make
879 	 * snmp_create_pdu() fetch SNMP_DEF_MAX_REPETITIONS rows)
880 	 */
881 	max_reps = is_vol ? 1 : 0;
882 
883 	pdu = snmp_create_pdu(SNMP_MSG_GETBULK, max_reps, oidstrs, n_oids, row);
884 	if (pdu == NULL)
885 		return;
886 
887 	LOGPDU(TAG_REQUEST_PDU, pdu);
888 
889 	/*
890 	 * Make an ASN.1 encoded packet from the PDU information
891 	 */
892 	if (snmp_make_packet(pdu) < 0) {
893 		snmp_free_pdu(pdu);
894 		return;
895 	}
896 
897 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
898 
899 	/*
900 	 * Send the request packet to the agent
901 	 */
902 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
903 		snmp_free_pdu(pdu);
904 		return;
905 	}
906 
907 	/*
908 	 * Receive response from the agent into the reply packet buffer
909 	 * in the request PDU
910 	 */
911 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
912 		snmp_free_pdu(pdu);
913 		return;
914 	}
915 
916 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
917 
918 	/*
919 	 * Parse the reply, validate the response and create a
920 	 * reply-PDU out of the information. Populate the mibcache
921 	 * with the received values.
922 	 */
923 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
924 	    pdu->reply_pktsz);
925 	if (reply_pdu) {
926 		LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
927 
928 		if (reply_pdu->errstat == SNMP_ERR_NOERROR)
929 			mibcache_populate(reply_pdu, is_vol);
930 
931 		snmp_free_pdu(reply_pdu);
932 	}
933 
934 	snmp_free_pdu(pdu);
935 }
936 
937 static snmp_pdu_t *
938 fetch_next(struct picl_snmphdl *smd, char *prefix, int row, int *snmp_syserr)
939 {
940 	snmp_pdu_t	*pdu, *reply_pdu;
941 
942 	LOGNEXT(TAG_CMD_REQUEST, prefix, row);
943 
944 	pdu = snmp_create_pdu(SNMP_MSG_GETNEXT, 0, prefix, 1, row);
945 	if (pdu == NULL)
946 		return (NULL);
947 
948 	LOGPDU(TAG_REQUEST_PDU, pdu);
949 
950 	if (snmp_make_packet(pdu) < 0) {
951 		snmp_free_pdu(pdu);
952 		return (NULL);
953 	}
954 
955 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
956 
957 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
958 		snmp_free_pdu(pdu);
959 		return (NULL);
960 	}
961 
962 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
963 		snmp_free_pdu(pdu);
964 		return (NULL);
965 	}
966 
967 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
968 
969 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
970 	    pdu->reply_pktsz);
971 
972 	LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
973 
974 	snmp_free_pdu(pdu);
975 
976 	return (reply_pdu);
977 }
978 
979 static int
980 snmp_send_request(struct picl_snmphdl *smd, snmp_pdu_t *pdu, int *snmp_syserr)
981 {
982 	extern int	errno;
983 #ifdef USE_SOCKETS
984 	int		ret;
985 #endif
986 
987 	if (smd->fd < 0)
988 		return (-1);
989 
990 	if (pdu == NULL || pdu->req_pkt == NULL)
991 		return (-1);
992 
993 #ifdef USE_SOCKETS
994 	ret = -1;
995 	while (ret < 0) {
996 		LOGIO(TAG_SENDTO, smd->fd, pdu->req_pkt, pdu->req_pktsz);
997 
998 		ret = sendto(smd->fd, pdu->req_pkt, pdu->req_pktsz, 0,
999 		    (struct sockaddr *)&smd->agent_addr,
1000 		    sizeof (struct sockaddr));
1001 		if (ret < 0 && errno != EINTR) {
1002 			return (-1);
1003 		}
1004 	}
1005 #else
1006 	LOGIO(TAG_WRITE, smd->fd, pdu->req_pkt, pdu->req_pktsz);
1007 
1008 	if (write(smd->fd, pdu->req_pkt, pdu->req_pktsz) < 0) {
1009 		if (snmp_syserr)
1010 			*snmp_syserr = errno;
1011 		return (-1);
1012 	}
1013 #endif
1014 
1015 #ifdef SNMP_DEBUG
1016 	snmp_nsends++;
1017 	snmp_sentbytes += pdu->req_pktsz;
1018 #endif
1019 
1020 	return (0);
1021 }
1022 
1023 static int
1024 snmp_recv_reply(struct picl_snmphdl *smd, snmp_pdu_t *pdu, int *snmp_syserr)
1025 {
1026 	struct dssnmp_info	snmp_info;
1027 	size_t	pktsz;
1028 	uchar_t	*pkt;
1029 	extern int errno;
1030 #ifdef USE_SOCKETS
1031 	struct sockaddr_in 	from;
1032 	int	fromlen;
1033 	ssize_t	msgsz;
1034 #endif
1035 
1036 	if (smd->fd < 0 || pdu == NULL)
1037 		return (-1);
1038 
1039 #ifdef USE_SOCKETS
1040 	if ((pkt = (uchar_t *)calloc(1, SNMP_MAX_RECV_PKTSZ)) == NULL)
1041 		return (-1);
1042 
1043 	fromlen = sizeof (struct sockaddr_in);
1044 
1045 	LOGIO(TAG_RECVFROM, smd->fd, pkt, SNMP_MAX_RECV_PKTSZ);
1046 
1047 	msgsz = recvfrom(smd->fd, pkt, SNMP_MAX_RECV_PKTSZ, 0,
1048 	    (struct sockaddr *)&from, &fromlen);
1049 	if (msgsz  < 0 || msgsz >= SNMP_MAX_RECV_PKTSZ) {
1050 		free(pkt);
1051 		return (-1);
1052 	}
1053 
1054 	pktsz = (size_t)msgsz;
1055 #else
1056 	LOGIO(TAG_IOCTL, smd->fd, DSSNMP_GETINFO, &snmp_info);
1057 
1058 	/*
1059 	 * The ioctl will block until we have snmp data available
1060 	 */
1061 	if (ioctl(smd->fd, DSSNMP_GETINFO, &snmp_info) < 0) {
1062 		if (snmp_syserr)
1063 			*snmp_syserr = errno;
1064 		return (-1);
1065 	}
1066 
1067 	pktsz = snmp_info.size;
1068 	if ((pkt = (uchar_t *)calloc(1, pktsz)) == NULL)
1069 		return (-1);
1070 
1071 	LOGIO(TAG_READ, smd->fd, pkt, pktsz);
1072 
1073 	if (read(smd->fd, pkt, pktsz) < 0) {
1074 		free(pkt);
1075 		if (snmp_syserr)
1076 			*snmp_syserr = errno;
1077 		return (-1);
1078 	}
1079 #endif
1080 
1081 	pdu->reply_pkt = pkt;
1082 	pdu->reply_pktsz = pktsz;
1083 
1084 #ifdef SNMP_DEBUG
1085 	snmp_nrecvs++;
1086 	snmp_rcvdbytes += pktsz;
1087 #endif
1088 
1089 	return (0);
1090 }
1091 
1092 static int
1093 mibcache_realloc(int hint)
1094 {
1095 	uint_t		count = (uint_t)hint;
1096 	nvlist_t	**p;
1097 
1098 	if (hint < 0)
1099 		return (-1);
1100 
1101 	(void) mutex_lock(&mibcache_lock);
1102 
1103 	if (hint < n_mibcache_rows) {
1104 		(void) mutex_unlock(&mibcache_lock);
1105 		return (0);
1106 	}
1107 
1108 	count =  ((count >> MIBCACHE_BLK_SHIFT) + 1) << MIBCACHE_BLK_SHIFT;
1109 
1110 	p = (nvlist_t **)calloc(count, sizeof (nvlist_t *));
1111 	if (p == NULL) {
1112 		(void) mutex_unlock(&mibcache_lock);
1113 		return (-1);
1114 	}
1115 
1116 	if (mibcache) {
1117 		(void) memcpy((void *) p, (void *) mibcache,
1118 		    n_mibcache_rows * sizeof (nvlist_t *));
1119 		free((void *) mibcache);
1120 	}
1121 
1122 	mibcache = p;
1123 	n_mibcache_rows = count;
1124 
1125 	(void) mutex_unlock(&mibcache_lock);
1126 
1127 	return (0);
1128 }
1129 
1130 
1131 /*
1132  * Scan each variable in the returned PDU's bindings and populate
1133  * the cache appropriately
1134  */
1135 static void
1136 mibcache_populate(snmp_pdu_t *pdu, int is_vol)
1137 {
1138 	pdu_varlist_t	*vp;
1139 	int		row, ret;
1140 	char		*oidstr;
1141 	struct timeval	tv;
1142 	int		tod;	/* in secs */
1143 	char		tod_str[MAX_INT_LEN];
1144 	int		ival_arr[2];
1145 	char		*sval_arr[2];
1146 
1147 	/*
1148 	 * If we're populating volatile properties, we also store a
1149 	 * timestamp with each property value. When we lookup, we
1150 	 * check the current time against this timestamp to determine
1151 	 * if we need to refetch the value or not (refetch if it has
1152 	 * been in for far too long).
1153 	 */
1154 	if (is_vol) {
1155 		if (gettimeofday(&tv, NULL) < 0)
1156 			tod = -1;
1157 		else
1158 			tod = (int)tv.tv_sec;
1159 
1160 		tod_str[0] = 0;
1161 		(void) snprintf(tod_str, MAX_INT_LEN, "%d", tod);
1162 
1163 		ival_arr[1] = tod;
1164 		sval_arr[1] = (char *)tod_str;
1165 	}
1166 
1167 	for (vp = pdu->vars; vp; vp = vp->nextvar) {
1168 		if (vp->type != ASN_INTEGER && vp->type != ASN_OCTET_STR &&
1169 		    vp->type != ASN_BIT_STR) {
1170 			continue;
1171 		}
1172 
1173 		if (vp->name == NULL || vp->val.str == NULL)
1174 			continue;
1175 
1176 		row = (vp->name)[vp->name_len-1];
1177 
1178 		(void) mutex_lock(&mibcache_lock);
1179 
1180 		if (row >= n_mibcache_rows) {
1181 			(void) mutex_unlock(&mibcache_lock);
1182 			if (mibcache_realloc(row) < 0)
1183 				continue;
1184 			(void) mutex_lock(&mibcache_lock);
1185 		}
1186 		ret = 0;
1187 		if (mibcache[row] == NULL)
1188 			ret = nvlist_alloc(&mibcache[row], NV_UNIQUE_NAME, 0);
1189 
1190 		(void) mutex_unlock(&mibcache_lock);
1191 
1192 		if (ret != 0)
1193 			continue;
1194 
1195 		/*
1196 		 * Convert the standard OID form into an oid string that
1197 		 * we can use as the key to lookup. Since we only search
1198 		 * by the prefix (mibcache is really an array of nvlist_t
1199 		 * pointers), ignore the leaf subid.
1200 		 */
1201 		oidstr = oid_to_oidstr(vp->name, vp->name_len - 1);
1202 		if (oidstr == NULL)
1203 			continue;
1204 
1205 		(void) mutex_lock(&mibcache_lock);
1206 
1207 		if (vp->type == ASN_INTEGER) {
1208 			if (is_vol) {
1209 				ival_arr[0] = *(vp->val.iptr);
1210 				(void) nvlist_add_int32_array(mibcache[row],
1211 				    oidstr, ival_arr, 2);
1212 			} else {
1213 				(void) nvlist_add_int32(mibcache[row],
1214 				    oidstr, *(vp->val.iptr));
1215 			}
1216 
1217 		} else if (vp->type == ASN_OCTET_STR) {
1218 			if (is_vol) {
1219 				sval_arr[0] = (char *)vp->val.str;
1220 				(void) nvlist_add_string_array(mibcache[row],
1221 				    oidstr, sval_arr, 2);
1222 			} else {
1223 				(void) nvlist_add_string(mibcache[row],
1224 				    oidstr, (const char *)(vp->val.str));
1225 			}
1226 		} else if (vp->type == ASN_BIT_STR) {
1227 			/*
1228 			 * We don't support yet bit string objects that are
1229 			 * volatile values.
1230 			 */
1231 			if (!is_vol) {
1232 				(void) nvlist_add_byte_array(mibcache[row],
1233 				    oidstr, (uchar_t *)(vp->val.str),
1234 				    (uint_t)vp->val_len);
1235 			}
1236 		}
1237 		(void) mutex_unlock(&mibcache_lock);
1238 
1239 		free(oidstr);
1240 	}
1241 }
1242 
1243 static char *
1244 oid_to_oidstr(oid *objid, size_t n_subids)
1245 {
1246 	char	*oidstr;
1247 	char	subid_str[MAX_INT_LEN];
1248 	int	i, isize;
1249 	size_t	oidstr_sz;
1250 
1251 	/*
1252 	 * ugly, but for now this will have to do.
1253 	 */
1254 	oidstr_sz = sizeof (subid_str) * n_subids;
1255 	oidstr = calloc(1, oidstr_sz);
1256 
1257 	for (i = 0; i < n_subids; i++) {
1258 		(void) memset(subid_str, 0, sizeof (subid_str));
1259 		isize = snprintf(subid_str, sizeof (subid_str), "%d",
1260 		    objid[i]);
1261 		if (isize >= sizeof (subid_str))
1262 			return (NULL);
1263 
1264 		(void) strlcat(oidstr, subid_str, oidstr_sz);
1265 		if (i < (n_subids - 1))
1266 			(void) strlcat(oidstr, ".", oidstr_sz);
1267 	}
1268 
1269 	return (oidstr);
1270 }
1271