]> git.proxmox.com Git - mirror_frr.git/blame - lib/smux.c
debianpkg: Remove -werror from Ubuntu 14.04 and 12.04 build to skip warnings from...
[mirror_frr.git] / lib / smux.c
CommitLineData
718e3744 1/* SNMP support
2 * Copyright (C) 1999 Kunihiro Ishiguro <kunihiro@zebra.org>
3 *
4 * This file is part of GNU Zebra.
5 *
6 * GNU Zebra is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * GNU Zebra is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GNU Zebra; see the file COPYING. If not, write to the Free
18 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
ac4d0be5 19 * 02111-1307, USA.
718e3744 20 */
21
22#include <zebra.h>
23
5986b66b 24#ifdef SNMP_SMUX
07661cb5 25#include <net-snmp/net-snmp-config.h>
fb62a3ce 26#include <net-snmp/net-snmp-includes.h>
718e3744 27
718e3744 28#include "log.h"
29#include "thread.h"
30#include "linklist.h"
31#include "command.h"
5e4fa164 32#include <lib/version.h>
718e3744 33#include "memory.h"
34#include "sockunion.h"
dd488a78 35#include "smux.h"
718e3744 36
3a4c9688
VB
37#define SMUX_PORT_DEFAULT 199
38
39#define SMUXMAXPKTSIZE 1500
40#define SMUXMAXSTRLEN 256
41
42#define SMUX_OPEN (ASN_APPLICATION | ASN_CONSTRUCTOR | 0)
43#define SMUX_CLOSE (ASN_APPLICATION | ASN_PRIMITIVE | 1)
44#define SMUX_RREQ (ASN_APPLICATION | ASN_CONSTRUCTOR | 2)
45#define SMUX_RRSP (ASN_APPLICATION | ASN_PRIMITIVE | 3)
46#define SMUX_SOUT (ASN_APPLICATION | ASN_PRIMITIVE | 4)
47
48#define SMUX_GET (ASN_CONTEXT | ASN_CONSTRUCTOR | 0)
49#define SMUX_GETNEXT (ASN_CONTEXT | ASN_CONSTRUCTOR | 1)
50#define SMUX_GETRSP (ASN_CONTEXT | ASN_CONSTRUCTOR | 2)
51#define SMUX_SET (ASN_CONTEXT | ASN_CONSTRUCTOR | 3)
52#define SMUX_TRAP (ASN_CONTEXT | ASN_CONSTRUCTOR | 4)
53
54#define SMUX_MAX_FAILURE 3
55
56/* SNMP tree. */
ac4d0be5 57struct subtree {
58 /* Tree's oid. */
59 oid name[MAX_OID_LEN];
60 u_char name_len;
3a4c9688 61
ac4d0be5 62 /* List of the variables. */
63 struct variable *variables;
3a4c9688 64
ac4d0be5 65 /* Length of the variables list. */
66 int variables_num;
3a4c9688 67
ac4d0be5 68 /* Width of the variables list. */
69 int variables_width;
3a4c9688 70
ac4d0be5 71 /* Registered flag. */
72 int registered;
3a4c9688
VB
73};
74
718e3744 75#define min(A,B) ((A) < (B) ? (A) : (B))
76
ac4d0be5 77enum smux_event { SMUX_SCHEDULE, SMUX_CONNECT, SMUX_READ };
718e3744 78
ac4d0be5 79void smux_event(enum smux_event, int);
6b0655a2 80
718e3744 81
82/* SMUX socket. */
83int smux_sock = -1;
84
85/* SMUX subtree list. */
86struct list *treelist;
87
88/* SMUX oid. */
c75105ab 89oid *smux_oid = NULL;
718e3744 90size_t smux_oid_len;
91
718e3744 92/* SMUX password. */
c75105ab 93char *smux_passwd = NULL;
718e3744 94
95/* SMUX read threads. */
96struct thread *smux_read_thread;
97
98/* SMUX connect thrads. */
99struct thread *smux_connect_thread;
100
101/* SMUX debug flag. */
102int debug_smux = 0;
103
104/* SMUX failure count. */
105int fail = 0;
106
107/* SMUX node. */
ac4d0be5 108static struct cmd_node smux_node = {
109 SMUX_NODE, "" /* SMUX has no interface. */
718e3744 110};
dd488a78 111
112/* thread master */
79159516 113static struct thread_master *smux_master;
6b0655a2 114
ac4d0be5 115static int oid_compare_part(oid *o1, int o1_len, oid *o2, int o2_len)
718e3744 116{
ac4d0be5 117 int i;
118
119 for (i = 0; i < min(o1_len, o2_len); i++) {
120 if (o1[i] < o2[i])
121 return -1;
122 else if (o1[i] > o2[i])
123 return 1;
124 }
125 if (o1_len < o2_len)
126 return -1;
127
128 return 0;
718e3744 129}
6b0655a2 130
ac4d0be5 131static void smux_oid_dump(const char *prefix, const oid *oid, size_t oid_len)
718e3744 132{
ac4d0be5 133 unsigned int i;
134 int first = 1;
135 char buf[MAX_OID_LEN * 3];
136
137 buf[0] = '\0';
138
139 for (i = 0; i < oid_len; i++) {
140 sprintf(buf + strlen(buf), "%s%d", first ? "" : ".",
141 (int)oid[i]);
142 first = 0;
143 }
144 zlog_debug("%s: %s", prefix, buf);
718e3744 145}
146
ac4d0be5 147static int smux_socket(void)
718e3744 148{
ac4d0be5 149 int ret;
150 struct addrinfo hints, *res0, *res;
151 int gai;
152 int sock = 0;
153
154 memset(&hints, 0, sizeof(hints));
155 hints.ai_family = PF_UNSPEC;
156 hints.ai_socktype = SOCK_STREAM;
157 gai = getaddrinfo(NULL, "smux", &hints, &res0);
158 if (gai == EAI_SERVICE) {
159 char servbuf[NI_MAXSERV];
160 sprintf(servbuf, "%d", SMUX_PORT_DEFAULT);
161 servbuf[sizeof(servbuf) - 1] = '\0';
162 gai = getaddrinfo(NULL, servbuf, &hints, &res0);
163 }
164 if (gai) {
165 zlog_warn("Cannot locate loopback service smux");
166 return -1;
167 }
168 for (res = res0; res; res = res->ai_next) {
169 if (res->ai_family != AF_INET && res->ai_family != AF_INET6)
170 continue;
171
172 sock = socket(res->ai_family, res->ai_socktype,
173 res->ai_protocol);
174 if (sock < 0)
175 continue;
176 sockopt_reuseaddr(sock);
177 sockopt_reuseport(sock);
178 ret = connect(sock, res->ai_addr, res->ai_addrlen);
179 if (ret < 0) {
180 close(sock);
181 sock = -1;
182 continue;
183 }
184 break;
718e3744 185 }
ac4d0be5 186 freeaddrinfo(res0);
187 if (sock < 0)
188 zlog_warn("Can't connect to SNMP agent with SMUX");
189 return sock;
718e3744 190}
191
ac4d0be5 192static void smux_getresp_send(oid objid[], size_t objid_len, long reqid,
193 long errstat, long errindex, u_char val_type,
194 void *arg, size_t arg_len)
718e3744 195{
ac4d0be5 196 u_char buf[BUFSIZ];
197 u_char *ptr, *h1, *h1e, *h2, *h2e;
198 size_t len, length;
199
200 ptr = buf;
201 len = BUFSIZ;
202 length = len;
203
204 if (debug_smux) {
205 zlog_debug("SMUX GETRSP send");
206 zlog_debug("SMUX GETRSP reqid: %ld", reqid);
207 }
208
209 h1 = ptr;
210 /* Place holder h1 for complete sequence */
211 ptr = asn_build_sequence(ptr, &len, (u_char)SMUX_GETRSP, 0);
212 h1e = ptr;
213
214 ptr = asn_build_int(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
215 | ASN_INTEGER),
216 &reqid, sizeof(reqid));
217
218 if (debug_smux)
219 zlog_debug("SMUX GETRSP errstat: %ld", errstat);
220
221 ptr = asn_build_int(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
222 | ASN_INTEGER),
223 &errstat, sizeof(errstat));
224 if (debug_smux)
225 zlog_debug("SMUX GETRSP errindex: %ld", errindex);
226
227 ptr = asn_build_int(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
228 | ASN_INTEGER),
229 &errindex, sizeof(errindex));
230
231 h2 = ptr;
232 /* Place holder h2 for one variable */
233 ptr = asn_build_sequence(ptr, &len,
234 (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), 0);
235 h2e = ptr;
236
237 ptr = snmp_build_var_op(ptr, objid, &objid_len, val_type, arg_len, arg,
238 &len);
239
240 /* Now variable size is known, fill in size */
241 asn_build_sequence(h2, &length,
242 (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), ptr - h2e);
243
244 /* Fill in size of whole sequence */
245 asn_build_sequence(h1, &length, (u_char)SMUX_GETRSP, ptr - h1e);
246
247 if (debug_smux)
248 zlog_debug("SMUX getresp send: %td", (ptr - buf));
249
250 send(smux_sock, buf, (ptr - buf), 0);
718e3744 251}
252
ac4d0be5 253static u_char *smux_var(u_char *ptr, size_t len, oid objid[], size_t *objid_len,
254 size_t *var_val_len, u_char *var_val_type,
255 void **var_value)
718e3744 256{
ac4d0be5 257 u_char type;
258 u_char val_type;
259 size_t val_len;
260 u_char *val;
261
262 if (debug_smux)
263 zlog_debug("SMUX var parse: len %zd", len);
264
265 /* Parse header. */
266 ptr = asn_parse_header(ptr, &len, &type);
267
268 if (debug_smux) {
269 zlog_debug("SMUX var parse: type %d len %zd", type, len);
270 zlog_debug("SMUX var parse: type must be %d",
271 (ASN_SEQUENCE | ASN_CONSTRUCTOR));
272 }
273
274 /* Parse var option. */
275 *objid_len = MAX_OID_LEN;
276 ptr = snmp_parse_var_op(ptr, objid, objid_len, &val_type, &val_len,
277 &val, &len);
278
279 if (var_val_len)
280 *var_val_len = val_len;
281
282 if (var_value)
283 *var_value = (void *)val;
284
285 if (var_val_type)
286 *var_val_type = val_type;
287
288 /* Requested object id length is objid_len. */
289 if (debug_smux)
290 smux_oid_dump("Request OID", objid, *objid_len);
291
292 if (debug_smux)
293 zlog_debug("SMUX val_type: %d", val_type);
294
295 /* Check request value type. */
296 if (debug_smux)
297 switch (val_type) {
298 case ASN_NULL:
299 /* In case of SMUX_GET or SMUX_GET_NEXT val_type is set
300 to
301 ASN_NULL. */
302 zlog_debug("ASN_NULL");
303 break;
304
305 case ASN_INTEGER:
306 zlog_debug("ASN_INTEGER");
307 break;
308 case ASN_COUNTER:
309 case ASN_GAUGE:
310 case ASN_TIMETICKS:
311 case ASN_UINTEGER:
312 zlog_debug("ASN_COUNTER");
313 break;
314 case ASN_COUNTER64:
315 zlog_debug("ASN_COUNTER64");
316 break;
317 case ASN_IPADDRESS:
318 zlog_debug("ASN_IPADDRESS");
319 break;
320 case ASN_OCTET_STR:
321 zlog_debug("ASN_OCTET_STR");
322 break;
323 case ASN_OPAQUE:
324 case ASN_NSAP:
325 case ASN_OBJECT_ID:
326 zlog_debug("ASN_OPAQUE");
327 break;
328 case SNMP_NOSUCHOBJECT:
329 zlog_debug("SNMP_NOSUCHOBJECT");
330 break;
331 case SNMP_NOSUCHINSTANCE:
332 zlog_debug("SNMP_NOSUCHINSTANCE");
333 break;
334 case SNMP_ENDOFMIBVIEW:
335 zlog_debug("SNMP_ENDOFMIBVIEW");
336 break;
337 case ASN_BIT_STR:
338 zlog_debug("ASN_BIT_STR");
339 break;
340 default:
341 zlog_debug("Unknown type");
342 break;
343 }
344 return ptr;
718e3744 345}
346
347/* NOTE: all 3 functions (smux_set, smux_get & smux_getnext) are based on
348 ucd-snmp smux and as such suppose, that the peer receives in the message
349 only one variable. Fortunately, IBM seems to do the same in AIX. */
350
ac4d0be5 351static int smux_set(oid *reqid, size_t *reqid_len, u_char val_type, void *val,
352 size_t val_len, int action)
718e3744 353{
ac4d0be5 354 int j;
355 struct subtree *subtree;
356 struct variable *v;
357 int subresult;
358 oid *suffix;
359 size_t suffix_len;
360 int result;
361 u_char *statP = NULL;
362 WriteMethod *write_method = NULL;
363 struct listnode *node, *nnode;
364
365 /* Check */
366 for (ALL_LIST_ELEMENTS(treelist, node, nnode, subtree)) {
367 subresult = oid_compare_part(reqid, *reqid_len, subtree->name,
368 subtree->name_len);
369
370 /* Subtree matched. */
371 if (subresult == 0) {
372 /* Prepare suffix. */
373 suffix = reqid + subtree->name_len;
374 suffix_len = *reqid_len - subtree->name_len;
375 result = subresult;
376
377 /* Check variables. */
378 for (j = 0; j < subtree->variables_num; j++) {
379 v = &subtree->variables[j];
380
381 /* Always check suffix */
382 result = oid_compare_part(suffix, suffix_len,
383 v->name, v->namelen);
384
385 /* This is exact match so result must be zero.
386 */
387 if (result == 0) {
388 if (debug_smux)
389 zlog_debug(
390 "SMUX function call index is %d",
391 v->magic);
392
393 statP = (*v->findVar)(
394 v, suffix, &suffix_len, 1,
395 &val_len, &write_method);
396
397 if (write_method) {
398 return (*write_method)(
399 action, val, val_type,
400 val_len, statP, suffix,
401 suffix_len);
402 } else {
403 return SNMP_ERR_READONLY;
404 }
405 }
406
407 /* If above execution is failed or oid is small
408 (so
409 there is no further match). */
410 if (result < 0)
411 return SNMP_ERR_NOSUCHNAME;
412 }
413 }
414 }
415 return SNMP_ERR_NOSUCHNAME;
718e3744 416}
417
ac4d0be5 418static int smux_get(oid *reqid, size_t *reqid_len, int exact, u_char *val_type,
419 void **val, size_t *val_len)
718e3744 420{
ac4d0be5 421 int j;
422 struct subtree *subtree;
423 struct variable *v;
424 int subresult;
425 oid *suffix;
426 size_t suffix_len;
427 int result;
428 WriteMethod *write_method = NULL;
429 struct listnode *node, *nnode;
430
431 /* Check */
432 for (ALL_LIST_ELEMENTS(treelist, node, nnode, subtree)) {
433 subresult = oid_compare_part(reqid, *reqid_len, subtree->name,
434 subtree->name_len);
435
436 /* Subtree matched. */
437 if (subresult == 0) {
438 /* Prepare suffix. */
439 suffix = reqid + subtree->name_len;
440 suffix_len = *reqid_len - subtree->name_len;
441 result = subresult;
442
443 /* Check variables. */
444 for (j = 0; j < subtree->variables_num; j++) {
445 v = &subtree->variables[j];
446
447 /* Always check suffix */
448 result = oid_compare_part(suffix, suffix_len,
449 v->name, v->namelen);
450
451 /* This is exact match so result must be zero.
452 */
453 if (result == 0) {
454 if (debug_smux)
455 zlog_debug(
456 "SMUX function call index is %d",
457 v->magic);
458
459 *val = (*v->findVar)(
460 v, suffix, &suffix_len, exact,
461 val_len, &write_method);
462
463 /* There is no instance. */
464 if (*val == NULL)
465 return SNMP_NOSUCHINSTANCE;
466
467 /* Call is suceed. */
468 *val_type = v->type;
469
470 return 0;
471 }
472
473 /* If above execution is failed or oid is small
474 (so
475 there is no further match). */
476 if (result < 0)
477 return SNMP_ERR_NOSUCHNAME;
478 }
718e3744 479 }
718e3744 480 }
ac4d0be5 481 return SNMP_ERR_NOSUCHNAME;
718e3744 482}
483
ac4d0be5 484static int smux_getnext(oid *reqid, size_t *reqid_len, int exact,
485 u_char *val_type, void **val, size_t *val_len)
718e3744 486{
ac4d0be5 487 int j;
488 oid save[MAX_OID_LEN];
489 int savelen = 0;
490 struct subtree *subtree;
491 struct variable *v;
492 int subresult;
493 oid *suffix;
494 size_t suffix_len;
495 int result;
496 WriteMethod *write_method = NULL;
497 struct listnode *node, *nnode;
498
499
500 /* Save incoming request. */
501 oid_copy(save, reqid, *reqid_len);
502 savelen = *reqid_len;
503
504 /* Check */
505 for (ALL_LIST_ELEMENTS(treelist, node, nnode, subtree)) {
506 subresult = oid_compare_part(reqid, *reqid_len, subtree->name,
507 subtree->name_len);
508
509 /* If request is in the tree. The agent has to make sure we
510 only receive requests we have registered for. */
511 /* Unfortunately, that's not true. In fact, a SMUX subagent has
512 to
513 behave as if it manages the whole SNMP MIB tree itself. It's
514 the
515 duty of the master agent to collect the best answer and
516 return it
517 to the manager. See RFC 1227 chapter 3.1.6 for the glory
518 details
519 :-). ucd-snmp really behaves bad here as it actually might
520 ask
521 multiple times for the same GETNEXT request as it throws away
522 the
523 answer when it expects it in a different subtree and might
524 come
525 back later with the very same request. --jochen */
526
527 if (subresult <= 0) {
528 /* Prepare suffix. */
529 suffix = reqid + subtree->name_len;
530 suffix_len = *reqid_len - subtree->name_len;
531 if (subresult < 0) {
532 oid_copy(reqid, subtree->name,
533 subtree->name_len);
534 *reqid_len = subtree->name_len;
535 }
536 for (j = 0; j < subtree->variables_num; j++) {
537 result = subresult;
538 v = &subtree->variables[j];
539
540 /* Next then check result >= 0. */
541 if (result == 0)
542 result = oid_compare_part(
543 suffix, suffix_len, v->name,
544 v->namelen);
545
546 if (result <= 0) {
547 if (debug_smux)
548 zlog_debug(
549 "SMUX function call index is %d",
550 v->magic);
551 if (result < 0) {
552 oid_copy(suffix, v->name,
553 v->namelen);
554 suffix_len = v->namelen;
555 }
556 *val = (*v->findVar)(
557 v, suffix, &suffix_len, exact,
558 val_len, &write_method);
559 *reqid_len =
560 suffix_len + subtree->name_len;
561 if (*val) {
562 *val_type = v->type;
563 return 0;
564 }
565 }
566 }
718e3744 567 }
718e3744 568 }
ac4d0be5 569 memcpy(reqid, save, savelen * sizeof(oid));
570 *reqid_len = savelen;
718e3744 571
ac4d0be5 572 return SNMP_ERR_NOSUCHNAME;
718e3744 573}
574
575/* GET message header. */
ac4d0be5 576static u_char *smux_parse_get_header(u_char *ptr, size_t *len, long *reqid)
718e3744 577{
ac4d0be5 578 u_char type;
579 long errstat;
580 long errindex;
718e3744 581
ac4d0be5 582 /* Request ID. */
583 ptr = asn_parse_int(ptr, len, &type, reqid, sizeof(*reqid));
718e3744 584
ac4d0be5 585 if (debug_smux)
586 zlog_debug("SMUX GET reqid: %d len: %d", (int)*reqid,
587 (int)*len);
718e3744 588
ac4d0be5 589 /* Error status. */
590 ptr = asn_parse_int(ptr, len, &type, &errstat, sizeof(errstat));
718e3744 591
ac4d0be5 592 if (debug_smux)
593 zlog_debug("SMUX GET errstat %ld len: %zd", errstat, *len);
718e3744 594
ac4d0be5 595 /* Error index. */
596 ptr = asn_parse_int(ptr, len, &type, &errindex, sizeof(errindex));
718e3744 597
ac4d0be5 598 if (debug_smux)
599 zlog_debug("SMUX GET errindex %ld len: %zd", errindex, *len);
718e3744 600
ac4d0be5 601 return ptr;
718e3744 602}
603
ac4d0be5 604static void smux_parse_set(u_char *ptr, size_t len, int action)
718e3744 605{
ac4d0be5 606 long reqid;
607 oid oid[MAX_OID_LEN];
608 size_t oid_len;
609 u_char val_type;
610 void *val;
611 size_t val_len;
612 int ret;
613
614 if (debug_smux)
615 zlog_debug("SMUX SET(%s) message parse: len %zd",
616 (RESERVE1 == action)
617 ? "RESERVE1"
618 : ((FREE == action) ? "FREE" : "COMMIT"),
619 len);
620
621 /* Parse SET message header. */
622 ptr = smux_parse_get_header(ptr, &len, &reqid);
623
624 /* Parse SET message object ID. */
625 ptr = smux_var(ptr, len, oid, &oid_len, &val_len, &val_type, &val);
626
627 ret = smux_set(oid, &oid_len, val_type, val, val_len, action);
628 if (debug_smux)
629 zlog_debug("SMUX SET ret %d", ret);
630
631 /* Return result. */
632 if (RESERVE1 == action)
633 smux_getresp_send(oid, oid_len, reqid, ret, 3, ASN_NULL, NULL,
634 0);
718e3744 635}
636
ac4d0be5 637static void smux_parse_get(u_char *ptr, size_t len, int exact)
718e3744 638{
ac4d0be5 639 long reqid;
640 oid oid[MAX_OID_LEN];
641 size_t oid_len;
642 u_char val_type;
643 void *val;
644 size_t val_len;
645 int ret;
646
647 if (debug_smux)
648 zlog_debug("SMUX GET message parse: len %zd", len);
649
650 /* Parse GET message header. */
651 ptr = smux_parse_get_header(ptr, &len, &reqid);
652
653 /* Parse GET message object ID. We needn't the value come */
654 ptr = smux_var(ptr, len, oid, &oid_len, NULL, NULL, NULL);
655
656 /* Traditional getstatptr. */
657 if (exact)
658 ret = smux_get(oid, &oid_len, exact, &val_type, &val, &val_len);
659 else
660 ret = smux_getnext(oid, &oid_len, exact, &val_type, &val,
661 &val_len);
662
663 /* Return result. */
664 if (ret == 0)
665 smux_getresp_send(oid, oid_len, reqid, 0, 0, val_type, val,
666 val_len);
667 else
668 smux_getresp_send(oid, oid_len, reqid, ret, 3, ASN_NULL, NULL,
669 0);
718e3744 670}
671
672/* Parse SMUX_CLOSE message. */
ac4d0be5 673static void smux_parse_close(u_char *ptr, int len)
718e3744 674{
ac4d0be5 675 long reason = 0;
676
677 while (len--) {
678 reason = (reason << 8) | (long)*ptr;
679 ptr++;
680 }
681 zlog_info("SMUX_CLOSE with reason: %ld", reason);
718e3744 682}
683
684/* SMUX_RRSP message. */
ac4d0be5 685static void smux_parse_rrsp(u_char *ptr, size_t len)
718e3744 686{
ac4d0be5 687 u_char val;
688 long errstat;
718e3744 689
ac4d0be5 690 ptr = asn_parse_int(ptr, &len, &val, &errstat, sizeof(errstat));
691
692 if (debug_smux)
693 zlog_debug("SMUX_RRSP value: %d errstat: %ld", val, errstat);
718e3744 694}
695
696/* Parse SMUX message. */
ac4d0be5 697static int smux_parse(u_char *ptr, size_t len)
718e3744 698{
ac4d0be5 699 /* This buffer we'll use for SOUT message. We could allocate it with
700 malloc and save only static pointer/lenght, but IMHO static
701 buffer is a faster solusion. */
702 static u_char sout_save_buff[SMUXMAXPKTSIZE];
703 static int sout_save_len = 0;
718e3744 704
ac4d0be5 705 int len_income = len; /* see note below: YYY */
706 u_char type;
707 u_char rollback;
718e3744 708
ac4d0be5 709 rollback = ptr[2]; /* important only for SMUX_SOUT */
718e3744 710
711process_rest: /* see note below: YYY */
712
ac4d0be5 713 /* Parse SMUX message type and subsequent length. */
714 ptr = asn_parse_header(ptr, &len, &type);
715
716 if (debug_smux)
717 zlog_debug("SMUX message received type: %d rest len: %zd", type,
718 len);
719
720 switch (type) {
721 case SMUX_OPEN:
722 /* Open must be not send from SNMP agent. */
723 zlog_warn("SMUX_OPEN received: resetting connection.");
724 return -1;
725 break;
726 case SMUX_RREQ:
727 /* SMUX_RREQ message is invalid for us. */
728 zlog_warn("SMUX_RREQ received: resetting connection.");
729 return -1;
730 break;
731 case SMUX_SOUT:
732 /* SMUX_SOUT message is now valied for us. */
733 if (debug_smux)
734 zlog_debug("SMUX_SOUT(%s)",
735 rollback ? "rollback" : "commit");
736
737 if (sout_save_len > 0) {
738 smux_parse_set(sout_save_buff, sout_save_len,
739 rollback ? FREE : COMMIT);
740 sout_save_len = 0;
741 } else
742 zlog_warn("SMUX_SOUT sout_save_len=%d - invalid",
743 (int)sout_save_len);
744
745 if (len_income > 3) {
746 /* YYY: this strange code has to solve the "slow peer"
747 problem: When agent sends SMUX_SOUT message it
748 doesn't
749 wait any responce and may send some next message to
750 subagent. Then the peer in 'smux_read()' will recieve
751 from socket the 'concatenated' buffer, contaning both
752 SMUX_SOUT message and the next one
753 (SMUX_GET/SMUX_GETNEXT/SMUX_GET). So we should check:
754 if
755 the buffer is longer than 3 ( length of SMUX_SOUT ),
756 we
757 must process the rest of it. This effect may be
758 observed
759 if 'debug_smux' is set to '1' */
760 ptr++;
761 len = len_income - 3;
762 goto process_rest;
763 }
764 break;
765 case SMUX_GETRSP:
766 /* SMUX_GETRSP message is invalid for us. */
767 zlog_warn("SMUX_GETRSP received: resetting connection.");
768 return -1;
769 break;
770 case SMUX_CLOSE:
771 /* Close SMUX connection. */
772 if (debug_smux)
773 zlog_debug("SMUX_CLOSE");
774 smux_parse_close(ptr, len);
775 return -1;
776 break;
777 case SMUX_RRSP:
778 /* This is response for register message. */
779 if (debug_smux)
780 zlog_debug("SMUX_RRSP");
781 smux_parse_rrsp(ptr, len);
782 break;
783 case SMUX_GET:
784 /* Exact request for object id. */
785 if (debug_smux)
786 zlog_debug("SMUX_GET");
787 smux_parse_get(ptr, len, 1);
788 break;
789 case SMUX_GETNEXT:
790 /* Next request for object id. */
791 if (debug_smux)
792 zlog_debug("SMUX_GETNEXT");
793 smux_parse_get(ptr, len, 0);
794 break;
795 case SMUX_SET:
796 /* SMUX_SET is supported with some limitations. */
797 if (debug_smux)
798 zlog_debug("SMUX_SET");
799
800 /* save the data for future SMUX_SOUT */
801 memcpy(sout_save_buff, ptr, len);
802 sout_save_len = len;
803 smux_parse_set(ptr, len, RESERVE1);
804 break;
805 default:
806 zlog_info("Unknown type: %d", type);
807 break;
808 }
809 return 0;
718e3744 810}
811
812/* SMUX message read function. */
ac4d0be5 813static int smux_read(struct thread *t)
718e3744 814{
ac4d0be5 815 int sock;
816 int len;
817 u_char buf[SMUXMAXPKTSIZE];
818 int ret;
819
820 /* Clear thread. */
821 sock = THREAD_FD(t);
822 smux_read_thread = NULL;
823
824 if (debug_smux)
825 zlog_debug("SMUX read start");
826
827 /* Read message from SMUX socket. */
828 len = recv(sock, buf, SMUXMAXPKTSIZE, 0);
829
830 if (len < 0) {
831 zlog_warn("Can't read all SMUX packet: %s",
832 safe_strerror(errno));
833 close(sock);
834 smux_sock = -1;
835 smux_event(SMUX_CONNECT, 0);
836 return -1;
837 }
838
839 if (len == 0) {
840 zlog_warn("SMUX connection closed: %d", sock);
841 close(sock);
842 smux_sock = -1;
843 smux_event(SMUX_CONNECT, 0);
844 return -1;
845 }
846
847 if (debug_smux)
848 zlog_debug("SMUX read len: %d", len);
849
850 /* Parse the message. */
851 ret = smux_parse(buf, len);
852
853 if (ret < 0) {
854 close(sock);
855 smux_sock = -1;
856 smux_event(SMUX_CONNECT, 0);
857 return -1;
858 }
859
860 /* Regiser read thread. */
861 smux_event(SMUX_READ, sock);
862
863 return 0;
718e3744 864}
865
ac4d0be5 866static int smux_open(int sock)
718e3744 867{
ac4d0be5 868 u_char buf[BUFSIZ];
869 u_char *ptr;
870 size_t len;
871 long version;
872 const char progname[] = FRR_SMUX_NAME "-" FRR_VERSION;
873
874 if (debug_smux) {
875 smux_oid_dump("SMUX open oid", smux_oid, smux_oid_len);
876 zlog_debug("SMUX open progname: %s", progname);
877 zlog_debug("SMUX open password: %s", smux_passwd);
878 }
879
880 ptr = buf;
881 len = BUFSIZ;
882
883 /* SMUX Header. As placeholder. */
884 ptr = asn_build_header(ptr, &len, (u_char)SMUX_OPEN, 0);
885
886 /* SMUX Open. */
887 version = 0;
888 ptr = asn_build_int(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
889 | ASN_INTEGER),
890 &version, sizeof(version));
891
892 /* SMUX connection oid. */
893 ptr = asn_build_objid(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
894 | ASN_OBJECT_ID),
895 smux_oid, smux_oid_len);
896
897 /* SMUX connection description. */
898 ptr = asn_build_string(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
899 | ASN_OCTET_STR),
900 (const u_char *)progname, strlen(progname));
901
902 /* SMUX connection password. */
903 ptr = asn_build_string(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
904 | ASN_OCTET_STR),
905 (u_char *)smux_passwd, strlen(smux_passwd));
906
907 /* Fill in real SMUX header. We exclude ASN header size (2). */
908 len = BUFSIZ;
909 asn_build_header(buf, &len, (u_char)SMUX_OPEN, (ptr - buf) - 2);
910
911 return send(sock, buf, (ptr - buf), 0);
718e3744 912}
913
b7c0d065
VB
914/* `ename` is ignored. Instead of using the provided enterprise OID,
915 the SMUX peer is used. This keep compatibility with the previous
916 versions of Quagga.
917
918 All other fields are used as they are intended. */
ac4d0be5 919int smux_trap(struct variable *vp, size_t vp_len, const oid *ename,
920 size_t enamelen, const oid *name, size_t namelen,
921 const oid *iname, size_t inamelen,
922 const struct trap_object *trapobj, size_t trapobjlen,
923 u_char sptrap)
718e3744 924{
ac4d0be5 925 unsigned int i;
926 u_char buf[BUFSIZ];
927 u_char *ptr;
928 size_t len, length;
929 struct in_addr addr;
930 unsigned long val;
931 u_char *h1, *h1e;
932
933 ptr = buf;
934 len = BUFSIZ;
935 length = len;
936
937 /* When SMUX connection is not established. */
938 if (smux_sock < 0)
939 return 0;
940
941 /* SMUX header. */
942 ptr = asn_build_header(ptr, &len, (u_char)SMUX_TRAP, 0);
943
944 /* Sub agent enterprise oid. */
945 ptr = asn_build_objid(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
946 | ASN_OBJECT_ID),
947 smux_oid, smux_oid_len);
948
949 /* IP address. */
950 addr.s_addr = 0;
951 ptr = asn_build_string(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
952 | ASN_IPADDRESS),
953 (u_char *)&addr, sizeof(addr));
954
955 /* Generic trap integer. */
956 val = SNMP_TRAP_ENTERPRISESPECIFIC;
957 ptr = asn_build_int(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
958 | ASN_INTEGER),
959 (long *)&val, sizeof(val));
960
961 /* Specific trap integer. */
962 val = sptrap;
963 ptr = asn_build_int(ptr, &len, (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE
964 | ASN_INTEGER),
965 (long *)&val, sizeof(val));
966
967 /* Timeticks timestamp. */
968 val = 0;
969 ptr = asn_build_unsigned_int(
970 ptr, &len,
971 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_TIMETICKS), &val,
972 sizeof(val));
973
974 /* Variables. */
975 h1 = ptr;
976 ptr = asn_build_sequence(ptr, &len,
977 (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), 0);
978
979
980 /* Iteration for each objects. */
981 h1e = ptr;
982 for (i = 0; i < trapobjlen; i++) {
983 int ret;
984 oid oid[MAX_OID_LEN];
985 size_t oid_len;
986 void *val;
987 size_t val_len;
988 u_char val_type;
989
990 /* Make OID. */
991 if (trapobj[i].namelen > 0) {
992 oid_copy(oid, name, namelen);
993 oid_copy(oid + namelen, trapobj[i].name,
994 trapobj[i].namelen);
995 oid_copy(oid + namelen + trapobj[i].namelen, iname,
996 inamelen);
997 oid_len = namelen + trapobj[i].namelen + inamelen;
998 } else {
999 oid_copy(oid, name, namelen);
1000 oid_copy(oid + namelen, trapobj[i].name,
1001 trapobj[i].namelen * (-1));
1002 oid_len = namelen + trapobj[i].namelen * (-1);
1003 }
1004
1005 if (debug_smux) {
1006 smux_oid_dump("Trap", name, namelen);
1007 if (trapobj[i].namelen < 0)
1008 smux_oid_dump("Trap", trapobj[i].name,
1009 (-1) * (trapobj[i].namelen));
1010 else {
1011 smux_oid_dump("Trap", trapobj[i].name,
1012 (trapobj[i].namelen));
1013 smux_oid_dump("Trap", iname, inamelen);
1014 }
1015 smux_oid_dump("Trap", oid, oid_len);
1016 zlog_info("BUFSIZ: %d // oid_len: %lu", BUFSIZ,
1017 (u_long)oid_len);
1018 }
1019
1020 ret = smux_get(oid, &oid_len, 1, &val_type, &val, &val_len);
1021
1022 if (debug_smux)
1023 zlog_debug("smux_get result %d", ret);
1024
1025 if (ret == 0)
1026 ptr = snmp_build_var_op(ptr, oid, &oid_len, val_type,
1027 val_len, val, &len);
1028 }
1029
1030 /* Now variable size is known, fill in size */
1031 asn_build_sequence(h1, &length,
1032 (u_char)(ASN_SEQUENCE | ASN_CONSTRUCTOR), ptr - h1e);
1033
1034 /* Fill in size of whole sequence */
1035 len = BUFSIZ;
1036 asn_build_header(buf, &len, (u_char)SMUX_TRAP, (ptr - buf) - 2);
1037
1038 return send(smux_sock, buf, (ptr - buf), 0);
718e3744 1039}
1040
ac4d0be5 1041static int smux_register(int sock)
718e3744 1042{
ac4d0be5 1043 u_char buf[BUFSIZ];
1044 u_char *ptr;
1045 int ret;
1046 size_t len;
1047 long priority;
1048 long operation;
1049 struct subtree *subtree;
1050 struct listnode *node, *nnode;
1051
1052 ret = 0;
1053
1054 for (ALL_LIST_ELEMENTS(treelist, node, nnode, subtree)) {
1055 ptr = buf;
1056 len = BUFSIZ;
1057
1058 /* SMUX RReq Header. */
1059 ptr = asn_build_header(ptr, &len, (u_char)SMUX_RREQ, 0);
1060
1061 /* Register MIB tree. */
1062 ptr = asn_build_objid(
1063 ptr, &len,
1064 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID),
1065 subtree->name, subtree->name_len);
1066
1067 /* Priority. */
1068 priority = -1;
1069 ptr = asn_build_int(
1070 ptr, &len,
1071 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
1072 &priority, sizeof(priority));
1073
1074 /* Operation. */
1075 operation = 2; /* Register R/W */
1076 ptr = asn_build_int(
1077 ptr, &len,
1078 (u_char)(ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER),
1079 &operation, sizeof(operation));
1080
1081 if (debug_smux) {
1082 smux_oid_dump("SMUX register oid", subtree->name,
1083 subtree->name_len);
1084 zlog_debug("SMUX register priority: %ld", priority);
1085 zlog_debug("SMUX register operation: %ld", operation);
1086 }
1087
1088 len = BUFSIZ;
1089 asn_build_header(buf, &len, (u_char)SMUX_RREQ, (ptr - buf) - 2);
1090 ret = send(sock, buf, (ptr - buf), 0);
1091 if (ret < 0)
1092 return ret;
1093 }
1094 return ret;
718e3744 1095}
1096
1097/* Try to connect to SNMP agent. */
ac4d0be5 1098static int smux_connect(struct thread *t)
718e3744 1099{
ac4d0be5 1100 int ret;
1101
1102 if (debug_smux)
1103 zlog_debug("SMUX connect try %d", fail + 1);
1104
1105 /* Clear thread poner of myself. */
1106 smux_connect_thread = NULL;
1107
1108 /* Make socket. Try to connect. */
1109 smux_sock = smux_socket();
1110 if (smux_sock < 0) {
1111 if (++fail < SMUX_MAX_FAILURE)
1112 smux_event(SMUX_CONNECT, 0);
1113 return 0;
1114 }
1115
1116 /* Send OPEN PDU. */
1117 ret = smux_open(smux_sock);
1118 if (ret < 0) {
1119 zlog_warn("SMUX open message send failed: %s",
1120 safe_strerror(errno));
1121 close(smux_sock);
1122 smux_sock = -1;
1123 if (++fail < SMUX_MAX_FAILURE)
1124 smux_event(SMUX_CONNECT, 0);
1125 return -1;
1126 }
1127
1128 /* Send any outstanding register PDUs. */
1129 ret = smux_register(smux_sock);
1130 if (ret < 0) {
1131 zlog_warn("SMUX register message send failed: %s",
1132 safe_strerror(errno));
1133 close(smux_sock);
1134 smux_sock = -1;
1135 if (++fail < SMUX_MAX_FAILURE)
1136 smux_event(SMUX_CONNECT, 0);
1137 return -1;
1138 }
1139
1140 /* Everything goes fine. */
1141 smux_event(SMUX_READ, smux_sock);
1142
1143 return 0;
718e3744 1144}
1145
1146/* Clear all SMUX related resources. */
ac4d0be5 1147static void smux_stop(void)
718e3744 1148{
ac4d0be5 1149 if (smux_read_thread) {
1150 thread_cancel(smux_read_thread);
1151 smux_read_thread = NULL;
1152 }
6b0655a2 1153
ac4d0be5 1154 if (smux_connect_thread) {
1155 thread_cancel(smux_connect_thread);
1156 smux_connect_thread = NULL;
1157 }
dd488a78 1158
ac4d0be5 1159 if (smux_sock >= 0) {
1160 close(smux_sock);
1161 smux_sock = -1;
1162 }
1163}
718e3744 1164
ac4d0be5 1165
1166void smux_event(enum smux_event event, int sock)
718e3744 1167{
ac4d0be5 1168 switch (event) {
1169 case SMUX_SCHEDULE:
1170 smux_connect_thread =
1171 thread_add_event(smux_master, smux_connect, NULL, 0);
1172 break;
1173 case SMUX_CONNECT:
1174 smux_connect_thread =
1175 thread_add_timer(smux_master, smux_connect, NULL, 10);
1176 break;
1177 case SMUX_READ:
1178 smux_read_thread =
1179 thread_add_read(smux_master, smux_read, NULL, sock);
1180 break;
1181 default:
1182 break;
1183 }
718e3744 1184}
6b0655a2 1185
ac4d0be5 1186static int smux_str2oid(const char *str, oid *oid, size_t *oid_len)
718e3744 1187{
ac4d0be5 1188 int len;
1189 int val;
1190
1191 len = 0;
1192 val = 0;
1193 *oid_len = 0;
1194
1195 if (*str == '.')
1196 str++;
1197 if (*str == '\0')
1198 return 0;
1199
1200 while (1) {
1201 if (!isdigit(*str))
1202 return -1;
1203
1204 while (isdigit(*str)) {
1205 val *= 10;
1206 val += (*str - '0');
1207 str++;
1208 }
718e3744 1209
ac4d0be5 1210 if (*str == '\0')
1211 break;
1212 if (*str != '.')
1213 return -1;
718e3744 1214
ac4d0be5 1215 oid[len++] = val;
1216 val = 0;
1217 str++;
1218 }
718e3744 1219
ac4d0be5 1220 oid[len++] = val;
1221 *oid_len = len;
718e3744 1222
ac4d0be5 1223 return 0;
718e3744 1224}
1225
ac4d0be5 1226static oid *smux_oid_dup(oid *objid, size_t objid_len)
718e3744 1227{
ac4d0be5 1228 oid *new;
718e3744 1229
ac4d0be5 1230 new = XMALLOC(MTYPE_TMP, sizeof(oid) * objid_len);
1231 oid_copy(new, objid, objid_len);
718e3744 1232
ac4d0be5 1233 return new;
718e3744 1234}
1235
ac4d0be5 1236static int smux_peer_oid(struct vty *vty, const char *oid_str,
1237 const char *passwd_str)
718e3744 1238{
ac4d0be5 1239 int ret;
1240 oid oid[MAX_OID_LEN];
1241 size_t oid_len;
1242
1243 ret = smux_str2oid(oid_str, oid, &oid_len);
1244 if (ret != 0) {
1245 vty_out(vty, "object ID malformed%s", VTY_NEWLINE);
1246 return CMD_WARNING;
1247 }
1248
1249 if (smux_oid) {
1250 free(smux_oid);
1251 smux_oid = NULL;
1252 }
1253
1254 /* careful, smux_passwd might point to string constant */
1255 if (smux_passwd) {
1256 free(smux_passwd);
1257 smux_passwd = NULL;
1258 }
1259
1260 smux_oid = smux_oid_dup(oid, oid_len);
1261 smux_oid_len = oid_len;
1262
1263 if (passwd_str)
1264 smux_passwd = strdup(passwd_str);
1265 else
1266 smux_passwd = strdup("");
1267
1268 return 0;
718e3744 1269}
1270
ac4d0be5 1271static int smux_peer_default(void)
718e3744 1272{
ac4d0be5 1273 if (smux_oid) {
1274 free(smux_oid);
1275 smux_oid = NULL;
1276 }
1277
1278 /* careful, smux_passwd might be pointing at string constant */
1279 if (smux_passwd) {
1280 free(smux_passwd);
1281 smux_passwd = NULL;
1282 }
1283
1284 return CMD_SUCCESS;
718e3744 1285}
1286
1287DEFUN (smux_peer,
1288 smux_peer_cmd,
1289 "smux peer OID",
1290 "SNMP MUX protocol settings\n"
1291 "SNMP MUX peer settings\n"
1292 "Object ID used in SMUX peering\n")
1293{
ac4d0be5 1294 int idx_oid = 2;
1295 if (smux_peer_oid(vty, argv[idx_oid]->arg, NULL) == 0) {
1296 smux_start();
1297 return CMD_SUCCESS;
1298 } else
1299 return CMD_WARNING;
718e3744 1300}
1301
1302DEFUN (smux_peer_password,
1303 smux_peer_password_cmd,
1304 "smux peer OID PASSWORD",
1305 "SNMP MUX protocol settings\n"
1306 "SNMP MUX peer settings\n"
1307 "SMUX peering object ID\n"
1308 "SMUX peering password\n")
1309{
ac4d0be5 1310 int idx_oid = 2;
1311 if (smux_peer_oid(vty, argv[idx_oid]->arg, argv[3]->rg) == 0) {
1312 smux_start();
1313 return CMD_SUCCESS;
1314 } else
1315 return CMD_WARNING;
718e3744 1316}
1317
1318DEFUN (no_smux_peer,
1319 no_smux_peer_cmd,
aa1c90a4 1320 "no smux peer [OID [PASSWORD]]",
718e3744 1321 NO_STR
1322 "SNMP MUX protocol settings\n"
aa1c90a4
QY
1323 "SNMP MUX peer settings\n"
1324 "SMUX peering object ID\n"
1325 "SMUX peering password\n")
718e3744 1326{
ac4d0be5 1327 smux_stop();
1328 return smux_peer_default();
718e3744 1329}
1330
ac4d0be5 1331static int config_write_smux(struct vty *vty)
718e3744 1332{
ac4d0be5 1333 int first = 1;
1334 unsigned int i;
1335
1336 if (smux_oid) {
1337 vty_out(vty, "smux peer ");
1338 for (i = 0; i < smux_oid_len; i++) {
1339 vty_out(vty, "%s%d", first ? "" : ".",
1340 (int)smux_oid[i]);
1341 first = 0;
1342 }
1343 vty_out(vty, " %s%s", smux_passwd, VTY_NEWLINE);
718e3744 1344 }
ac4d0be5 1345 return 0;
718e3744 1346}
1347
1348/* Register subtree to smux master tree. */
ac4d0be5 1349void smux_register_mib(const char *descr, struct variable *var, size_t width,
1350 int num, oid name[], size_t namelen)
718e3744 1351{
ac4d0be5 1352 struct subtree *tree;
1353
1354 tree = (struct subtree *)malloc(sizeof(struct subtree));
1355 oid_copy(tree->name, name, namelen);
1356 tree->name_len = namelen;
1357 tree->variables = var;
1358 tree->variables_num = num;
1359 tree->variables_width = width;
1360 tree->registered = 0;
1361 listnode_add_sort(treelist, tree);
718e3744 1362}
1363
718e3744 1364/* Compare function to keep treelist sorted */
ac4d0be5 1365static int smux_tree_cmp(struct subtree *tree1, struct subtree *tree2)
718e3744 1366{
ac4d0be5 1367 return oid_compare(tree1->name, tree1->name_len, tree2->name,
1368 tree2->name_len);
718e3744 1369}
1370
1371/* Initialize some values then schedule first SMUX connection. */
ac4d0be5 1372void smux_init(struct thread_master *tm)
718e3744 1373{
ac4d0be5 1374 assert(tm);
1375 /* copy callers thread master */
1376 smux_master = tm;
1377
1378 /* Make MIB tree. */
1379 treelist = list_new();
1380 treelist->cmp = (int (*)(void *, void *))smux_tree_cmp;
1381
1382 /* Install commands. */
1383 install_node(&smux_node, config_write_smux);
1384
1385 install_element(CONFIG_NODE, &smux_peer_cmd);
1386 install_element(CONFIG_NODE, &smux_peer_password_cmd);
1387 install_element(CONFIG_NODE, &no_smux_peer_cmd);
1388 install_element(CONFIG_NODE, &no_smux_peer_oid_cmd);
1389 install_element(CONFIG_NODE, &no_smux_peer_oid_password_cmd);
718e3744 1390}
1391
ac4d0be5 1392void smux_start(void)
718e3744 1393{
ac4d0be5 1394 /* Close any existing connections. */
1395 smux_stop();
a56ef883 1396
ac4d0be5 1397 /* Schedule first connection. */
1398 smux_event(SMUX_SCHEDULE, 0);
718e3744 1399}
5986b66b 1400#endif /* SNMP_SMUX */