]> git.proxmox.com Git - mirror_frr.git/blob - ospfclient/ospf_apiclient.c
[autoconf] bugs 162,303,178: Fix 'present but can not be compiled' warnings
[mirror_frr.git] / ospfclient / ospf_apiclient.c
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 = XMALLOC (MTYPE_OSPF_APICLIENT, sizeof (struct ospf_apiclient));
263 memset (new, 0, sizeof (struct ospf_apiclient));
264
265 /* Initialize socket descriptors for sync and async channels */
266 new->fd_sync = fd1;
267 new->fd_async = fd2;
268
269 return new;
270 }
271
272 int
273 ospf_apiclient_close (struct ospf_apiclient *oclient)
274 {
275
276 if (oclient->fd_sync >= 0)
277 {
278 close (oclient->fd_sync);
279 }
280
281 if (oclient->fd_async >= 0)
282 {
283 close (oclient->fd_async);
284 }
285
286 /* Free client structure */
287 XFREE (MTYPE_OSPF_APICLIENT, oclient);
288 return 0;
289 }
290
291 /* -----------------------------------------------------------
292 * Followings are functions to send a request to OSPFd
293 * -----------------------------------------------------------
294 */
295
296 /* Send synchronous request, wait for reply */
297 static int
298 ospf_apiclient_send_request (struct ospf_apiclient *oclient, struct msg *msg)
299 {
300 u_int32_t reqseq;
301 struct msg_reply *msgreply;
302 int rc;
303
304 /* NB: Given "msg" is freed inside this function. */
305
306 /* Remember the sequence number of the request */
307 reqseq = ntohl (msg->hdr.msgseq);
308
309 /* Write message to OSPFd */
310 rc = msg_write (oclient->fd_sync, msg);
311 msg_free (msg);
312
313 if (rc < 0)
314 {
315 return -1;
316 }
317
318 /* Wait for reply *//* NB: New "msg" is allocated by "msg_read()". */
319 msg = msg_read (oclient->fd_sync);
320 if (!msg)
321 return -1;
322
323 assert (msg->hdr.msgtype == MSG_REPLY);
324 assert (ntohl (msg->hdr.msgseq) == reqseq);
325
326 msgreply = (struct msg_reply *) STREAM_DATA (msg->s);
327 rc = msgreply->errcode;
328 msg_free (msg);
329
330 return rc;
331 }
332
333
334 /* -----------------------------------------------------------
335 * Helper functions
336 * -----------------------------------------------------------
337 */
338
339 static u_int32_t
340 ospf_apiclient_get_seqnr (void)
341 {
342 static u_int32_t seqnr = MIN_SEQ;
343 u_int32_t tmp;
344
345 tmp = seqnr;
346 /* Increment sequence number */
347 if (seqnr < MAX_SEQ)
348 {
349 seqnr++;
350 }
351 else
352 {
353 seqnr = MIN_SEQ;
354 }
355 return tmp;
356 }
357
358 /* -----------------------------------------------------------
359 * API to access OSPF daemon by client applications.
360 * -----------------------------------------------------------
361 */
362
363 /*
364 * Synchronous request to register opaque type.
365 */
366 int
367 ospf_apiclient_register_opaque_type (struct ospf_apiclient *cl,
368 u_char ltype, u_char otype)
369 {
370 struct msg *msg;
371 int rc;
372
373 /* just put 1 as a sequence number. */
374 msg = new_msg_register_opaque_type (ospf_apiclient_get_seqnr (),
375 ltype, otype);
376 if (!msg)
377 {
378 fprintf (stderr, "new_msg_register_opaque_type failed\n");
379 return -1;
380 }
381
382 rc = ospf_apiclient_send_request (cl, msg);
383 return rc;
384 }
385
386 /*
387 * Synchronous request to synchronize with OSPF's LSDB.
388 * Two steps required: register_event in order to get
389 * dynamic updates and LSDB_Sync.
390 */
391 int
392 ospf_apiclient_sync_lsdb (struct ospf_apiclient *oclient)
393 {
394 struct msg *msg;
395 int rc;
396 struct lsa_filter_type filter;
397
398 filter.typemask = 0xFFFF; /* all LSAs */
399 filter.origin = ANY_ORIGIN;
400 filter.num_areas = 0; /* all Areas. */
401
402 msg = new_msg_register_event (ospf_apiclient_get_seqnr (), &filter);
403 if (!msg)
404 {
405 fprintf (stderr, "new_msg_register_event failed\n");
406 return -1;
407 }
408 rc = ospf_apiclient_send_request (oclient, msg);
409
410 if (rc != 0)
411 goto out;
412
413 msg = new_msg_sync_lsdb (ospf_apiclient_get_seqnr (), &filter);
414 if (!msg)
415 {
416 fprintf (stderr, "new_msg_sync_lsdb failed\n");
417 return -1;
418 }
419 rc = ospf_apiclient_send_request (oclient, msg);
420
421 out:
422 return rc;
423 }
424
425 /*
426 * Synchronous request to originate or update an LSA.
427 */
428
429 int
430 ospf_apiclient_lsa_originate (struct ospf_apiclient *oclient,
431 struct in_addr ifaddr,
432 struct in_addr area_id,
433 u_char lsa_type,
434 u_char opaque_type, u_int32_t opaque_id,
435 void *opaquedata, int opaquelen)
436 {
437 struct msg *msg;
438 int rc;
439 u_char buf[OSPF_MAX_LSA_SIZE];
440 struct lsa_header *lsah;
441 u_int32_t tmp;
442
443
444 /* We can only originate opaque LSAs */
445 if (!IS_OPAQUE_LSA (lsa_type))
446 {
447 fprintf (stderr, "Cannot originate non-opaque LSA type %d\n", lsa_type);
448 return OSPF_API_ILLEGALLSATYPE;
449 }
450
451 /* Make a new LSA from parameters */
452 lsah = (struct lsa_header *) buf;
453 lsah->ls_age = 0;
454 lsah->options = 0;
455 lsah->type = lsa_type;
456
457 tmp = SET_OPAQUE_LSID (opaque_type, opaque_id);
458 lsah->id.s_addr = htonl (tmp);
459 lsah->adv_router.s_addr = 0;
460 lsah->ls_seqnum = 0;
461 lsah->checksum = 0;
462 lsah->length = htons (sizeof (struct lsa_header) + opaquelen);
463
464 memcpy (((u_char *) lsah) + sizeof (struct lsa_header), opaquedata,
465 opaquelen);
466
467 msg = new_msg_originate_request (ospf_apiclient_get_seqnr (),
468 ifaddr, area_id, lsah);
469 if (!msg)
470 {
471 fprintf (stderr, "new_msg_originate_request failed\n");
472 return OSPF_API_NOMEMORY;
473 }
474
475 rc = ospf_apiclient_send_request (oclient, msg);
476 return rc;
477 }
478
479 int
480 ospf_apiclient_lsa_delete (struct ospf_apiclient *oclient,
481 struct in_addr area_id, u_char lsa_type,
482 u_char opaque_type, u_int32_t opaque_id)
483 {
484 struct msg *msg;
485 int rc;
486
487 /* Only opaque LSA can be deleted */
488 if (!IS_OPAQUE_LSA (lsa_type))
489 {
490 fprintf (stderr, "Cannot delete non-opaque LSA type %d\n", lsa_type);
491 return OSPF_API_ILLEGALLSATYPE;
492 }
493
494 /* opaque_id is in host byte order and will be converted
495 * to network byte order by new_msg_delete_request */
496 msg = new_msg_delete_request (ospf_apiclient_get_seqnr (),
497 area_id, lsa_type, opaque_type, opaque_id);
498
499 rc = ospf_apiclient_send_request (oclient, msg);
500 return rc;
501 }
502
503 /* -----------------------------------------------------------
504 * Followings are handlers for messages from OSPF daemon
505 * -----------------------------------------------------------
506 */
507
508 static void
509 ospf_apiclient_handle_ready (struct ospf_apiclient *oclient, struct msg *msg)
510 {
511 struct msg_ready_notify *r;
512 r = (struct msg_ready_notify *) STREAM_DATA (msg->s);
513
514 /* Invoke registered callback function. */
515 if (oclient->ready_notify)
516 {
517 (oclient->ready_notify) (r->lsa_type, r->opaque_type, r->addr);
518 }
519 }
520
521 static void
522 ospf_apiclient_handle_new_if (struct ospf_apiclient *oclient, struct msg *msg)
523 {
524 struct msg_new_if *n;
525 n = (struct msg_new_if *) STREAM_DATA (msg->s);
526
527 /* Invoke registered callback function. */
528 if (oclient->new_if)
529 {
530 (oclient->new_if) (n->ifaddr, n->area_id);
531 }
532 }
533
534 static void
535 ospf_apiclient_handle_del_if (struct ospf_apiclient *oclient, struct msg *msg)
536 {
537 struct msg_del_if *d;
538 d = (struct msg_del_if *) STREAM_DATA (msg->s);
539
540 /* Invoke registered callback function. */
541 if (oclient->del_if)
542 {
543 (oclient->del_if) (d->ifaddr);
544 }
545 }
546
547 static void
548 ospf_apiclient_handle_ism_change (struct ospf_apiclient *oclient,
549 struct msg *msg)
550 {
551 struct msg_ism_change *m;
552 m = (struct msg_ism_change *) STREAM_DATA (msg->s);
553
554 /* Invoke registered callback function. */
555 if (oclient->ism_change)
556 {
557 (oclient->ism_change) (m->ifaddr, m->area_id, m->status);
558 }
559
560 }
561
562 static void
563 ospf_apiclient_handle_nsm_change (struct ospf_apiclient *oclient,
564 struct msg *msg)
565 {
566 struct msg_nsm_change *m;
567 m = (struct msg_nsm_change *) STREAM_DATA (msg->s);
568
569 /* Invoke registered callback function. */
570 if (oclient->nsm_change)
571 {
572 (oclient->nsm_change) (m->ifaddr, m->nbraddr, m->router_id, m->status);
573 }
574 }
575
576 static void
577 ospf_apiclient_handle_lsa_update (struct ospf_apiclient *oclient,
578 struct msg *msg)
579 {
580 struct msg_lsa_change_notify *cn;
581 struct lsa_header *lsa;
582 int lsalen;
583
584 cn = (struct msg_lsa_change_notify *) STREAM_DATA (msg->s);
585
586 /* Extract LSA from message */
587 lsalen = ntohs (cn->data.length);
588 lsa = XMALLOC (MTYPE_OSPF_APICLIENT, lsalen);
589 if (!lsa)
590 {
591 fprintf (stderr, "LSA update: Cannot allocate memory for LSA\n");
592 return;
593 }
594 memcpy (lsa, &(cn->data), lsalen);
595
596 /* Invoke registered update callback function */
597 if (oclient->update_notify)
598 {
599 (oclient->update_notify) (cn->ifaddr, cn->area_id,
600 cn->is_self_originated, lsa);
601 }
602
603 /* free memory allocated by ospf apiclient library */
604 XFREE (MTYPE_OSPF_APICLIENT, lsa);
605 }
606
607 static void
608 ospf_apiclient_handle_lsa_delete (struct ospf_apiclient *oclient,
609 struct msg *msg)
610 {
611 struct msg_lsa_change_notify *cn;
612 struct lsa_header *lsa;
613 int lsalen;
614
615 cn = (struct msg_lsa_change_notify *) STREAM_DATA (msg->s);
616
617 /* Extract LSA from message */
618 lsalen = ntohs (cn->data.length);
619 lsa = XMALLOC (MTYPE_OSPF_APICLIENT, lsalen);
620 if (!lsa)
621 {
622 fprintf (stderr, "LSA delete: Cannot allocate memory for LSA\n");
623 return;
624 }
625 memcpy (lsa, &(cn->data), lsalen);
626
627 /* Invoke registered update callback function */
628 if (oclient->delete_notify)
629 {
630 (oclient->delete_notify) (cn->ifaddr, cn->area_id,
631 cn->is_self_originated, lsa);
632 }
633
634 /* free memory allocated by ospf apiclient library */
635 XFREE (MTYPE_OSPF_APICLIENT, lsa);
636 }
637
638 static void
639 ospf_apiclient_msghandle (struct ospf_apiclient *oclient, struct msg *msg)
640 {
641 /* Call message handler function. */
642 switch (msg->hdr.msgtype)
643 {
644 case MSG_READY_NOTIFY:
645 ospf_apiclient_handle_ready (oclient, msg);
646 break;
647 case MSG_NEW_IF:
648 ospf_apiclient_handle_new_if (oclient, msg);
649 break;
650 case MSG_DEL_IF:
651 ospf_apiclient_handle_del_if (oclient, msg);
652 break;
653 case MSG_ISM_CHANGE:
654 ospf_apiclient_handle_ism_change (oclient, msg);
655 break;
656 case MSG_NSM_CHANGE:
657 ospf_apiclient_handle_nsm_change (oclient, msg);
658 break;
659 case MSG_LSA_UPDATE_NOTIFY:
660 ospf_apiclient_handle_lsa_update (oclient, msg);
661 break;
662 case MSG_LSA_DELETE_NOTIFY:
663 ospf_apiclient_handle_lsa_delete (oclient, msg);
664 break;
665 default:
666 fprintf (stderr, "ospf_apiclient_read: Unknown message type: %d\n",
667 msg->hdr.msgtype);
668 break;
669 }
670 }
671
672 /* -----------------------------------------------------------
673 * Callback handler registration
674 * -----------------------------------------------------------
675 */
676
677 void
678 ospf_apiclient_register_callback (struct ospf_apiclient *oclient,
679 void (*ready_notify) (u_char lsa_type,
680 u_char opaque_type,
681 struct in_addr addr),
682 void (*new_if) (struct in_addr ifaddr,
683 struct in_addr area_id),
684 void (*del_if) (struct in_addr ifaddr),
685 void (*ism_change) (struct in_addr ifaddr,
686 struct in_addr area_id,
687 u_char status),
688 void (*nsm_change) (struct in_addr ifaddr,
689 struct in_addr nbraddr,
690 struct in_addr
691 router_id,
692 u_char status),
693 void (*update_notify) (struct in_addr
694 ifaddr,
695 struct in_addr
696 area_id,
697 u_char self_origin,
698 struct lsa_header *
699 lsa),
700 void (*delete_notify) (struct in_addr
701 ifaddr,
702 struct in_addr
703 area_id,
704 u_char self_origin,
705 struct lsa_header *
706 lsa))
707 {
708 assert (oclient);
709 assert (update_notify);
710
711 /* Register callback function */
712 oclient->ready_notify = ready_notify;
713 oclient->new_if = new_if;
714 oclient->del_if = del_if;
715 oclient->ism_change = ism_change;
716 oclient->nsm_change = nsm_change;
717 oclient->update_notify = update_notify;
718 oclient->delete_notify = delete_notify;
719 }
720
721 /* -----------------------------------------------------------
722 * Asynchronous message handling
723 * -----------------------------------------------------------
724 */
725
726 int
727 ospf_apiclient_handle_async (struct ospf_apiclient *oclient)
728 {
729 struct msg *msg;
730
731 /* Get a message */
732 msg = msg_read (oclient->fd_async);
733
734 if (!msg)
735 {
736 /* Connection broke down */
737 return -1;
738 }
739
740 /* Handle message */
741 ospf_apiclient_msghandle (oclient, msg);
742
743 /* Don't forget to free this message */
744 msg_free (msg);
745
746 return 0;
747 }