Annotation of embedaddon/quagga/ospfclient/ospf_apiclient.c, revision 1.1.1.1
1.1 misho 1: /*
2: * Client side of OSPF API.
3: * Copyright (C) 2001, 2002, 2003 Ralph Keller
4: *
5: * This file is part of GNU Zebra.
6: *
7: * GNU Zebra is free software; you can redistribute it and/or modify
8: * it under the terms of the GNU General Public License as published
9: * by the Free Software Foundation; either version 2, or (at your
10: * option) any later version.
11: *
12: * GNU Zebra is distributed in the hope that it will be useful, but
13: * WITHOUT ANY WARRANTY; without even the implied warranty of
14: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15: * General Public License for more details.
16: *
17: * You should have received a copy of the GNU General Public License
18: * along with GNU Zebra; see the file COPYING. If not, write to the
19: * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20: * Boston, MA 02111-1307, USA.
21: */
22:
23: #include <zebra.h>
24:
25: #include <lib/version.h>
26: #include "getopt.h"
27: #include "thread.h"
28: #include "prefix.h"
29: #include "linklist.h"
30: #include "if.h"
31: #include "vector.h"
32: #include "vty.h"
33: #include "command.h"
34: #include "filter.h"
35: #include "stream.h"
36: #include "log.h"
37: #include "memory.h"
38:
39: #include "ospfd/ospfd.h"
40: #include "ospfd/ospf_interface.h"
41: #include "ospfd/ospf_asbr.h"
42: #include "ospfd/ospf_lsa.h"
43: #include "ospfd/ospf_opaque.h"
44: #include "ospfd/ospf_lsdb.h"
45: #include "ospfd/ospf_neighbor.h"
46: #include "ospfd/ospf_dump.h"
47: #include "ospfd/ospf_zebra.h"
48: #include "ospfd/ospf_api.h"
49:
50: #include "ospf_apiclient.h"
51:
52: /* Backlog for listen */
53: #define BACKLOG 5
54:
55: /* -----------------------------------------------------------
56: * Forward declarations
57: * -----------------------------------------------------------
58: */
59:
60: void ospf_apiclient_handle_reply (struct ospf_apiclient *oclient,
61: struct msg *msg);
62: void ospf_apiclient_handle_update_notify (struct ospf_apiclient *oclient,
63: struct msg *msg);
64: void ospf_apiclient_handle_delete_notify (struct ospf_apiclient *oclient,
65: struct msg *msg);
66:
67: /* -----------------------------------------------------------
68: * Initialization
69: * -----------------------------------------------------------
70: */
71:
72: static unsigned short
73: ospf_apiclient_getport (void)
74: {
75: struct servent *sp = getservbyname ("ospfapi", "tcp");
76:
77: return sp ? ntohs (sp->s_port) : OSPF_API_SYNC_PORT;
78: }
79:
80: /* -----------------------------------------------------------
81: * Followings are functions for connection management
82: * -----------------------------------------------------------
83: */
84:
85: struct ospf_apiclient *
86: ospf_apiclient_connect (char *host, int syncport)
87: {
88: struct sockaddr_in myaddr_sync;
89: struct sockaddr_in myaddr_async;
90: struct sockaddr_in peeraddr;
91: struct hostent *hp;
92: struct ospf_apiclient *new;
93: int size = 0;
94: unsigned int peeraddrlen;
95: int async_server_sock;
96: int fd1, fd2;
97: int ret;
98: int on = 1;
99:
100: /* There are two connections between the client and the server.
101: First the client opens a connection for synchronous requests/replies
102: to the server. The server will accept this connection and
103: as a reaction open a reverse connection channel for
104: asynchronous messages. */
105:
106: async_server_sock = socket (AF_INET, SOCK_STREAM, 0);
107: if (async_server_sock < 0)
108: {
109: fprintf (stderr,
110: "ospf_apiclient_connect: creating async socket failed\n");
111: return NULL;
112: }
113:
114: /* Prepare socket for asynchronous messages */
115: /* Initialize async address structure */
116: memset (&myaddr_async, 0, sizeof (struct sockaddr_in));
117: myaddr_async.sin_family = AF_INET;
118: myaddr_async.sin_addr.s_addr = htonl (INADDR_ANY);
119: myaddr_async.sin_port = htons (syncport+1);
120: size = sizeof (struct sockaddr_in);
121: #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
122: myaddr_async.sin_len = size;
123: #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
124:
125: /* This is a server socket, reuse addr and port */
126: ret = setsockopt (async_server_sock, SOL_SOCKET,
127: SO_REUSEADDR, (void *) &on, sizeof (on));
128: if (ret < 0)
129: {
130: fprintf (stderr, "ospf_apiclient_connect: SO_REUSEADDR failed\n");
131: close (async_server_sock);
132: return NULL;
133: }
134:
135: #ifdef SO_REUSEPORT
136: ret = setsockopt (async_server_sock, SOL_SOCKET, SO_REUSEPORT,
137: (void *) &on, sizeof (on));
138: if (ret < 0)
139: {
140: fprintf (stderr, "ospf_apiclient_connect: SO_REUSEPORT failed\n");
141: close (async_server_sock);
142: return NULL;
143: }
144: #endif /* SO_REUSEPORT */
145:
146: /* Bind socket to address structure */
147: ret = bind (async_server_sock, (struct sockaddr *) &myaddr_async, size);
148: if (ret < 0)
149: {
150: fprintf (stderr, "ospf_apiclient_connect: bind async socket failed\n");
151: close (async_server_sock);
152: return NULL;
153: }
154:
155: /* Wait for reverse channel connection establishment from server */
156: ret = listen (async_server_sock, BACKLOG);
157: if (ret < 0)
158: {
159: fprintf (stderr, "ospf_apiclient_connect: listen: %s\n", safe_strerror (errno));
160: close (async_server_sock);
161: return NULL;
162: }
163:
164: /* Make connection for synchronous requests and connect to server */
165: /* Resolve address of server */
166: hp = gethostbyname (host);
167: if (!hp)
168: {
169: fprintf (stderr, "ospf_apiclient_connect: no such host %s\n", host);
170: close (async_server_sock);
171: return NULL;
172: }
173:
174: fd1 = socket (AF_INET, SOCK_STREAM, 0);
175: if (fd1 < 0)
176: {
177: fprintf (stderr,
178: "ospf_apiclient_connect: creating sync socket failed\n");
179: return NULL;
180: }
181:
182:
183: /* Reuse addr and port */
184: ret = setsockopt (fd1, SOL_SOCKET,
185: SO_REUSEADDR, (void *) &on, sizeof (on));
186: if (ret < 0)
187: {
188: fprintf (stderr, "ospf_apiclient_connect: SO_REUSEADDR failed\n");
189: close (fd1);
190: return NULL;
191: }
192:
193: #ifdef SO_REUSEPORT
194: ret = setsockopt (fd1, SOL_SOCKET, SO_REUSEPORT,
195: (void *) &on, sizeof (on));
196: if (ret < 0)
197: {
198: fprintf (stderr, "ospf_apiclient_connect: SO_REUSEPORT failed\n");
199: close (fd1);
200: return NULL;
201: }
202: #endif /* SO_REUSEPORT */
203:
204:
205: /* Bind sync socket to address structure. This is needed since we
206: want the sync port number on a fixed port number. The reverse
207: async channel will be at this port+1 */
208:
209: memset (&myaddr_sync, 0, sizeof (struct sockaddr_in));
210: myaddr_sync.sin_family = AF_INET;
211: myaddr_sync.sin_port = htons (syncport);
212: #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
213: myaddr_sync.sin_len = sizeof (struct sockaddr_in);
214: #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
215:
216: ret = bind (fd1, (struct sockaddr *) &myaddr_sync, size);
217: if (ret < 0)
218: {
219: fprintf (stderr, "ospf_apiclient_connect: bind sync socket failed\n");
220: close (fd1);
221: return NULL;
222: }
223:
224: /* Prepare address structure for connect */
225: memcpy (&myaddr_sync.sin_addr, hp->h_addr, hp->h_length);
226: myaddr_sync.sin_family = AF_INET;
227: myaddr_sync.sin_port = htons(ospf_apiclient_getport ());
228: #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
229: myaddr_sync.sin_len = sizeof (struct sockaddr_in);
230: #endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */
231:
232: /* Now establish synchronous channel with OSPF daemon */
233: ret = connect (fd1, (struct sockaddr *) &myaddr_sync,
234: sizeof (struct sockaddr_in));
235: if (ret < 0)
236: {
237: fprintf (stderr, "ospf_apiclient_connect: sync connect failed\n");
238: close (async_server_sock);
239: close (fd1);
240: return NULL;
241: }
242:
243: /* Accept reverse connection */
244: peeraddrlen = sizeof (struct sockaddr_in);
245: memset (&peeraddr, 0, peeraddrlen);
246:
247: fd2 =
248: accept (async_server_sock, (struct sockaddr *) &peeraddr, &peeraddrlen);
249: if (fd2 < 0)
250: {
251: fprintf (stderr, "ospf_apiclient_connect: accept async failed\n");
252: close (async_server_sock);
253: close (fd1);
254: return NULL;
255: }
256:
257: /* Server socket is not needed anymore since we are not accepting more
258: connections */
259: close (async_server_sock);
260:
261: /* Create new client-side instance */
262: new = XCALLOC (MTYPE_OSPF_APICLIENT, sizeof (struct ospf_apiclient));
263:
264: /* Initialize socket descriptors for sync and async channels */
265: new->fd_sync = fd1;
266: new->fd_async = fd2;
267:
268: return new;
269: }
270:
271: int
272: ospf_apiclient_close (struct ospf_apiclient *oclient)
273: {
274:
275: if (oclient->fd_sync >= 0)
276: {
277: close (oclient->fd_sync);
278: }
279:
280: if (oclient->fd_async >= 0)
281: {
282: close (oclient->fd_async);
283: }
284:
285: /* Free client structure */
286: XFREE (MTYPE_OSPF_APICLIENT, oclient);
287: return 0;
288: }
289:
290: /* -----------------------------------------------------------
291: * Followings are functions to send a request to OSPFd
292: * -----------------------------------------------------------
293: */
294:
295: /* Send synchronous request, wait for reply */
296: static int
297: ospf_apiclient_send_request (struct ospf_apiclient *oclient, struct msg *msg)
298: {
299: u_int32_t reqseq;
300: struct msg_reply *msgreply;
301: int rc;
302:
303: /* NB: Given "msg" is freed inside this function. */
304:
305: /* Remember the sequence number of the request */
306: reqseq = ntohl (msg->hdr.msgseq);
307:
308: /* Write message to OSPFd */
309: rc = msg_write (oclient->fd_sync, msg);
310: msg_free (msg);
311:
312: if (rc < 0)
313: {
314: return -1;
315: }
316:
317: /* Wait for reply *//* NB: New "msg" is allocated by "msg_read()". */
318: msg = msg_read (oclient->fd_sync);
319: if (!msg)
320: return -1;
321:
322: assert (msg->hdr.msgtype == MSG_REPLY);
323: assert (ntohl (msg->hdr.msgseq) == reqseq);
324:
325: msgreply = (struct msg_reply *) STREAM_DATA (msg->s);
326: rc = msgreply->errcode;
327: msg_free (msg);
328:
329: return rc;
330: }
331:
332:
333: /* -----------------------------------------------------------
334: * Helper functions
335: * -----------------------------------------------------------
336: */
337:
338: static u_int32_t
339: ospf_apiclient_get_seqnr (void)
340: {
341: static u_int32_t seqnr = MIN_SEQ;
342: u_int32_t tmp;
343:
344: tmp = seqnr;
345: /* Increment sequence number */
346: if (seqnr < MAX_SEQ)
347: {
348: seqnr++;
349: }
350: else
351: {
352: seqnr = MIN_SEQ;
353: }
354: return tmp;
355: }
356:
357: /* -----------------------------------------------------------
358: * API to access OSPF daemon by client applications.
359: * -----------------------------------------------------------
360: */
361:
362: /*
363: * Synchronous request to register opaque type.
364: */
365: int
366: ospf_apiclient_register_opaque_type (struct ospf_apiclient *cl,
367: u_char ltype, u_char otype)
368: {
369: struct msg *msg;
370: int rc;
371:
372: /* just put 1 as a sequence number. */
373: msg = new_msg_register_opaque_type (ospf_apiclient_get_seqnr (),
374: ltype, otype);
375: if (!msg)
376: {
377: fprintf (stderr, "new_msg_register_opaque_type failed\n");
378: return -1;
379: }
380:
381: rc = ospf_apiclient_send_request (cl, msg);
382: return rc;
383: }
384:
385: /*
386: * Synchronous request to synchronize with OSPF's LSDB.
387: * Two steps required: register_event in order to get
388: * dynamic updates and LSDB_Sync.
389: */
390: int
391: ospf_apiclient_sync_lsdb (struct ospf_apiclient *oclient)
392: {
393: struct msg *msg;
394: int rc;
395: struct lsa_filter_type filter;
396:
397: filter.typemask = 0xFFFF; /* all LSAs */
398: filter.origin = ANY_ORIGIN;
399: filter.num_areas = 0; /* all Areas. */
400:
401: msg = new_msg_register_event (ospf_apiclient_get_seqnr (), &filter);
402: if (!msg)
403: {
404: fprintf (stderr, "new_msg_register_event failed\n");
405: return -1;
406: }
407: rc = ospf_apiclient_send_request (oclient, msg);
408:
409: if (rc != 0)
410: goto out;
411:
412: msg = new_msg_sync_lsdb (ospf_apiclient_get_seqnr (), &filter);
413: if (!msg)
414: {
415: fprintf (stderr, "new_msg_sync_lsdb failed\n");
416: return -1;
417: }
418: rc = ospf_apiclient_send_request (oclient, msg);
419:
420: out:
421: return rc;
422: }
423:
424: /*
425: * Synchronous request to originate or update an LSA.
426: */
427:
428: int
429: ospf_apiclient_lsa_originate (struct ospf_apiclient *oclient,
430: struct in_addr ifaddr,
431: struct in_addr area_id,
432: u_char lsa_type,
433: u_char opaque_type, u_int32_t opaque_id,
434: void *opaquedata, int opaquelen)
435: {
436: struct msg *msg;
437: int rc;
438: u_char buf[OSPF_MAX_LSA_SIZE];
439: struct lsa_header *lsah;
440: u_int32_t tmp;
441:
442:
443: /* We can only originate opaque LSAs */
444: if (!IS_OPAQUE_LSA (lsa_type))
445: {
446: fprintf (stderr, "Cannot originate non-opaque LSA type %d\n", lsa_type);
447: return OSPF_API_ILLEGALLSATYPE;
448: }
449:
450: /* Make a new LSA from parameters */
451: lsah = (struct lsa_header *) buf;
452: lsah->ls_age = 0;
453: lsah->options = 0;
454: lsah->type = lsa_type;
455:
456: tmp = SET_OPAQUE_LSID (opaque_type, opaque_id);
457: lsah->id.s_addr = htonl (tmp);
458: lsah->adv_router.s_addr = 0;
459: lsah->ls_seqnum = 0;
460: lsah->checksum = 0;
461: lsah->length = htons (sizeof (struct lsa_header) + opaquelen);
462:
463: memcpy (((u_char *) lsah) + sizeof (struct lsa_header), opaquedata,
464: opaquelen);
465:
466: msg = new_msg_originate_request (ospf_apiclient_get_seqnr (),
467: ifaddr, area_id, lsah);
468: if (!msg)
469: {
470: fprintf (stderr, "new_msg_originate_request failed\n");
471: return OSPF_API_NOMEMORY;
472: }
473:
474: rc = ospf_apiclient_send_request (oclient, msg);
475: return rc;
476: }
477:
478: int
479: ospf_apiclient_lsa_delete (struct ospf_apiclient *oclient,
480: struct in_addr area_id, u_char lsa_type,
481: u_char opaque_type, u_int32_t opaque_id)
482: {
483: struct msg *msg;
484: int rc;
485:
486: /* Only opaque LSA can be deleted */
487: if (!IS_OPAQUE_LSA (lsa_type))
488: {
489: fprintf (stderr, "Cannot delete non-opaque LSA type %d\n", lsa_type);
490: return OSPF_API_ILLEGALLSATYPE;
491: }
492:
493: /* opaque_id is in host byte order and will be converted
494: * to network byte order by new_msg_delete_request */
495: msg = new_msg_delete_request (ospf_apiclient_get_seqnr (),
496: area_id, lsa_type, opaque_type, opaque_id);
497:
498: rc = ospf_apiclient_send_request (oclient, msg);
499: return rc;
500: }
501:
502: /* -----------------------------------------------------------
503: * Followings are handlers for messages from OSPF daemon
504: * -----------------------------------------------------------
505: */
506:
507: static void
508: ospf_apiclient_handle_ready (struct ospf_apiclient *oclient, struct msg *msg)
509: {
510: struct msg_ready_notify *r;
511: r = (struct msg_ready_notify *) STREAM_DATA (msg->s);
512:
513: /* Invoke registered callback function. */
514: if (oclient->ready_notify)
515: {
516: (oclient->ready_notify) (r->lsa_type, r->opaque_type, r->addr);
517: }
518: }
519:
520: static void
521: ospf_apiclient_handle_new_if (struct ospf_apiclient *oclient, struct msg *msg)
522: {
523: struct msg_new_if *n;
524: n = (struct msg_new_if *) STREAM_DATA (msg->s);
525:
526: /* Invoke registered callback function. */
527: if (oclient->new_if)
528: {
529: (oclient->new_if) (n->ifaddr, n->area_id);
530: }
531: }
532:
533: static void
534: ospf_apiclient_handle_del_if (struct ospf_apiclient *oclient, struct msg *msg)
535: {
536: struct msg_del_if *d;
537: d = (struct msg_del_if *) STREAM_DATA (msg->s);
538:
539: /* Invoke registered callback function. */
540: if (oclient->del_if)
541: {
542: (oclient->del_if) (d->ifaddr);
543: }
544: }
545:
546: static void
547: ospf_apiclient_handle_ism_change (struct ospf_apiclient *oclient,
548: struct msg *msg)
549: {
550: struct msg_ism_change *m;
551: m = (struct msg_ism_change *) STREAM_DATA (msg->s);
552:
553: /* Invoke registered callback function. */
554: if (oclient->ism_change)
555: {
556: (oclient->ism_change) (m->ifaddr, m->area_id, m->status);
557: }
558:
559: }
560:
561: static void
562: ospf_apiclient_handle_nsm_change (struct ospf_apiclient *oclient,
563: struct msg *msg)
564: {
565: struct msg_nsm_change *m;
566: m = (struct msg_nsm_change *) STREAM_DATA (msg->s);
567:
568: /* Invoke registered callback function. */
569: if (oclient->nsm_change)
570: {
571: (oclient->nsm_change) (m->ifaddr, m->nbraddr, m->router_id, m->status);
572: }
573: }
574:
575: static void
576: ospf_apiclient_handle_lsa_update (struct ospf_apiclient *oclient,
577: struct msg *msg)
578: {
579: struct msg_lsa_change_notify *cn;
580: struct lsa_header *lsa;
581: int lsalen;
582:
583: cn = (struct msg_lsa_change_notify *) STREAM_DATA (msg->s);
584:
585: /* Extract LSA from message */
586: lsalen = ntohs (cn->data.length);
587: lsa = XMALLOC (MTYPE_OSPF_APICLIENT, lsalen);
588: if (!lsa)
589: {
590: fprintf (stderr, "LSA update: Cannot allocate memory for LSA\n");
591: return;
592: }
593: memcpy (lsa, &(cn->data), lsalen);
594:
595: /* Invoke registered update callback function */
596: if (oclient->update_notify)
597: {
598: (oclient->update_notify) (cn->ifaddr, cn->area_id,
599: cn->is_self_originated, lsa);
600: }
601:
602: /* free memory allocated by ospf apiclient library */
603: XFREE (MTYPE_OSPF_APICLIENT, lsa);
604: }
605:
606: static void
607: ospf_apiclient_handle_lsa_delete (struct ospf_apiclient *oclient,
608: struct msg *msg)
609: {
610: struct msg_lsa_change_notify *cn;
611: struct lsa_header *lsa;
612: int lsalen;
613:
614: cn = (struct msg_lsa_change_notify *) STREAM_DATA (msg->s);
615:
616: /* Extract LSA from message */
617: lsalen = ntohs (cn->data.length);
618: lsa = XMALLOC (MTYPE_OSPF_APICLIENT, lsalen);
619: if (!lsa)
620: {
621: fprintf (stderr, "LSA delete: Cannot allocate memory for LSA\n");
622: return;
623: }
624: memcpy (lsa, &(cn->data), lsalen);
625:
626: /* Invoke registered update callback function */
627: if (oclient->delete_notify)
628: {
629: (oclient->delete_notify) (cn->ifaddr, cn->area_id,
630: cn->is_self_originated, lsa);
631: }
632:
633: /* free memory allocated by ospf apiclient library */
634: XFREE (MTYPE_OSPF_APICLIENT, lsa);
635: }
636:
637: static void
638: ospf_apiclient_msghandle (struct ospf_apiclient *oclient, struct msg *msg)
639: {
640: /* Call message handler function. */
641: switch (msg->hdr.msgtype)
642: {
643: case MSG_READY_NOTIFY:
644: ospf_apiclient_handle_ready (oclient, msg);
645: break;
646: case MSG_NEW_IF:
647: ospf_apiclient_handle_new_if (oclient, msg);
648: break;
649: case MSG_DEL_IF:
650: ospf_apiclient_handle_del_if (oclient, msg);
651: break;
652: case MSG_ISM_CHANGE:
653: ospf_apiclient_handle_ism_change (oclient, msg);
654: break;
655: case MSG_NSM_CHANGE:
656: ospf_apiclient_handle_nsm_change (oclient, msg);
657: break;
658: case MSG_LSA_UPDATE_NOTIFY:
659: ospf_apiclient_handle_lsa_update (oclient, msg);
660: break;
661: case MSG_LSA_DELETE_NOTIFY:
662: ospf_apiclient_handle_lsa_delete (oclient, msg);
663: break;
664: default:
665: fprintf (stderr, "ospf_apiclient_read: Unknown message type: %d\n",
666: msg->hdr.msgtype);
667: break;
668: }
669: }
670:
671: /* -----------------------------------------------------------
672: * Callback handler registration
673: * -----------------------------------------------------------
674: */
675:
676: void
677: ospf_apiclient_register_callback (struct ospf_apiclient *oclient,
678: void (*ready_notify) (u_char lsa_type,
679: u_char opaque_type,
680: struct in_addr addr),
681: void (*new_if) (struct in_addr ifaddr,
682: struct in_addr area_id),
683: void (*del_if) (struct in_addr ifaddr),
684: void (*ism_change) (struct in_addr ifaddr,
685: struct in_addr area_id,
686: u_char status),
687: void (*nsm_change) (struct in_addr ifaddr,
688: struct in_addr nbraddr,
689: struct in_addr
690: router_id,
691: u_char status),
692: void (*update_notify) (struct in_addr
693: ifaddr,
694: struct in_addr
695: area_id,
696: u_char self_origin,
697: struct lsa_header *
698: lsa),
699: void (*delete_notify) (struct in_addr
700: ifaddr,
701: struct in_addr
702: area_id,
703: u_char self_origin,
704: struct lsa_header *
705: lsa))
706: {
707: assert (oclient);
708: assert (update_notify);
709:
710: /* Register callback function */
711: oclient->ready_notify = ready_notify;
712: oclient->new_if = new_if;
713: oclient->del_if = del_if;
714: oclient->ism_change = ism_change;
715: oclient->nsm_change = nsm_change;
716: oclient->update_notify = update_notify;
717: oclient->delete_notify = delete_notify;
718: }
719:
720: /* -----------------------------------------------------------
721: * Asynchronous message handling
722: * -----------------------------------------------------------
723: */
724:
725: int
726: ospf_apiclient_handle_async (struct ospf_apiclient *oclient)
727: {
728: struct msg *msg;
729:
730: /* Get a message */
731: msg = msg_read (oclient->fd_async);
732:
733: if (!msg)
734: {
735: /* Connection broke down */
736: return -1;
737: }
738:
739: /* Handle message */
740: ospf_apiclient_msghandle (oclient, msg);
741:
742: /* Don't forget to free this message */
743: msg_free (msg);
744:
745: return 0;
746: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>