Annotation of embedaddon/quagga/bgpd/bgp_ecommunity.c, revision 1.1.1.4
1.1 misho 1: /* BGP Extended Communities Attribute
2: Copyright (C) 2000 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
19: 02111-1307, USA. */
20:
21: #include <zebra.h>
22:
23: #include "hash.h"
24: #include "memory.h"
25: #include "prefix.h"
26: #include "command.h"
1.1.1.4 ! misho 27: #include "filter.h"
1.1 misho 28:
29: #include "bgpd/bgpd.h"
30: #include "bgpd/bgp_ecommunity.h"
31: #include "bgpd/bgp_aspath.h"
32:
33: /* Hash of community attribute. */
34: static struct hash *ecomhash;
1.1.1.4 ! misho 35:
1.1 misho 36: /* Allocate a new ecommunities. */
37: static struct ecommunity *
38: ecommunity_new (void)
39: {
40: return (struct ecommunity *) XCALLOC (MTYPE_ECOMMUNITY,
41: sizeof (struct ecommunity));
42: }
43:
44: /* Allocate ecommunities. */
45: void
46: ecommunity_free (struct ecommunity **ecom)
47: {
48: if ((*ecom)->val)
49: XFREE (MTYPE_ECOMMUNITY_VAL, (*ecom)->val);
50: if ((*ecom)->str)
51: XFREE (MTYPE_ECOMMUNITY_STR, (*ecom)->str);
52: XFREE (MTYPE_ECOMMUNITY, *ecom);
53: ecom = NULL;
54: }
55:
56: /* Add a new Extended Communities value to Extended Communities
57: Attribute structure. When the value is already exists in the
58: structure, we don't add the value. Newly added value is sorted by
59: numerical order. When the value is added to the structure return 1
60: else return 0. */
61: static int
62: ecommunity_add_val (struct ecommunity *ecom, struct ecommunity_val *eval)
63: {
64: u_int8_t *p;
65: int ret;
66: int c;
67:
68: /* When this is fist value, just add it. */
69: if (ecom->val == NULL)
70: {
71: ecom->size++;
72: ecom->val = XMALLOC (MTYPE_ECOMMUNITY_VAL, ecom_length (ecom));
73: memcpy (ecom->val, eval->val, ECOMMUNITY_SIZE);
74: return 1;
75: }
76:
77: /* If the value already exists in the structure return 0. */
78: c = 0;
79: for (p = ecom->val; c < ecom->size; p += ECOMMUNITY_SIZE, c++)
80: {
81: ret = memcmp (p, eval->val, ECOMMUNITY_SIZE);
82: if (ret == 0)
83: return 0;
84: if (ret > 0)
85: break;
86: }
87:
88: /* Add the value to the structure with numerical sorting. */
89: ecom->size++;
90: ecom->val = XREALLOC (MTYPE_ECOMMUNITY_VAL, ecom->val, ecom_length (ecom));
91:
92: memmove (ecom->val + (c + 1) * ECOMMUNITY_SIZE,
93: ecom->val + c * ECOMMUNITY_SIZE,
94: (ecom->size - 1 - c) * ECOMMUNITY_SIZE);
95: memcpy (ecom->val + c * ECOMMUNITY_SIZE, eval->val, ECOMMUNITY_SIZE);
96:
97: return 1;
98: }
99:
100: /* This function takes pointer to Extended Communites strucutre then
101: create a new Extended Communities structure by uniq and sort each
102: Extended Communities value. */
1.1.1.2 misho 103: struct ecommunity *
1.1 misho 104: ecommunity_uniq_sort (struct ecommunity *ecom)
105: {
106: int i;
107: struct ecommunity *new;
108: struct ecommunity_val *eval;
109:
110: if (! ecom)
111: return NULL;
112:
113: new = ecommunity_new ();
114:
115: for (i = 0; i < ecom->size; i++)
116: {
117: eval = (struct ecommunity_val *) (ecom->val + (i * ECOMMUNITY_SIZE));
118: ecommunity_add_val (new, eval);
119: }
120: return new;
121: }
122:
123: /* Parse Extended Communites Attribute in BGP packet. */
124: struct ecommunity *
125: ecommunity_parse (u_int8_t *pnt, u_short length)
126: {
127: struct ecommunity tmp;
128: struct ecommunity *new;
129:
130: /* Length check. */
131: if (length % ECOMMUNITY_SIZE)
132: return NULL;
133:
134: /* Prepare tmporary structure for making a new Extended Communities
135: Attribute. */
136: tmp.size = length / ECOMMUNITY_SIZE;
137: tmp.val = pnt;
138:
139: /* Create a new Extended Communities Attribute by uniq and sort each
140: Extended Communities value */
141: new = ecommunity_uniq_sort (&tmp);
142:
143: return ecommunity_intern (new);
144: }
145:
146: /* Duplicate the Extended Communities Attribute structure. */
147: struct ecommunity *
148: ecommunity_dup (struct ecommunity *ecom)
149: {
150: struct ecommunity *new;
151:
152: new = XCALLOC (MTYPE_ECOMMUNITY, sizeof (struct ecommunity));
153: new->size = ecom->size;
154: if (new->size)
155: {
156: new->val = XMALLOC (MTYPE_ECOMMUNITY_VAL, ecom->size * ECOMMUNITY_SIZE);
157: memcpy (new->val, ecom->val, ecom->size * ECOMMUNITY_SIZE);
158: }
159: else
160: new->val = NULL;
161: return new;
162: }
163:
164: /* Retrun string representation of communities attribute. */
165: char *
166: ecommunity_str (struct ecommunity *ecom)
167: {
168: if (! ecom->str)
169: ecom->str = ecommunity_ecom2str (ecom, ECOMMUNITY_FORMAT_DISPLAY);
170: return ecom->str;
171: }
172:
173: /* Merge two Extended Communities Attribute structure. */
174: struct ecommunity *
175: ecommunity_merge (struct ecommunity *ecom1, struct ecommunity *ecom2)
176: {
177: if (ecom1->val)
178: ecom1->val = XREALLOC (MTYPE_ECOMMUNITY_VAL, ecom1->val,
179: (ecom1->size + ecom2->size) * ECOMMUNITY_SIZE);
180: else
181: ecom1->val = XMALLOC (MTYPE_ECOMMUNITY_VAL,
182: (ecom1->size + ecom2->size) * ECOMMUNITY_SIZE);
183:
184: memcpy (ecom1->val + (ecom1->size * ECOMMUNITY_SIZE),
185: ecom2->val, ecom2->size * ECOMMUNITY_SIZE);
186: ecom1->size += ecom2->size;
187:
188: return ecom1;
189: }
190:
191: /* Intern Extended Communities Attribute. */
192: struct ecommunity *
193: ecommunity_intern (struct ecommunity *ecom)
194: {
195: struct ecommunity *find;
196:
197: assert (ecom->refcnt == 0);
198:
199: find = (struct ecommunity *) hash_get (ecomhash, ecom, hash_alloc_intern);
200:
201: if (find != ecom)
202: ecommunity_free (&ecom);
203:
204: find->refcnt++;
205:
206: if (! find->str)
207: find->str = ecommunity_ecom2str (find, ECOMMUNITY_FORMAT_DISPLAY);
208:
209: return find;
210: }
211:
212: /* Unintern Extended Communities Attribute. */
213: void
214: ecommunity_unintern (struct ecommunity **ecom)
215: {
216: struct ecommunity *ret;
217:
218: if ((*ecom)->refcnt)
219: (*ecom)->refcnt--;
220:
221: /* Pull off from hash. */
222: if ((*ecom)->refcnt == 0)
223: {
224: /* Extended community must be in the hash. */
225: ret = (struct ecommunity *) hash_release (ecomhash, *ecom);
226: assert (ret != NULL);
227:
228: ecommunity_free (ecom);
229: }
230: }
231:
232: /* Utinity function to make hash key. */
233: unsigned int
234: ecommunity_hash_make (void *arg)
235: {
236: const struct ecommunity *ecom = arg;
1.1.1.3 misho 237: int size = ecom->size * ECOMMUNITY_SIZE;
238: u_int8_t *pnt = ecom->val;
239: unsigned int key = 0;
1.1 misho 240: int c;
241:
1.1.1.3 misho 242: for (c = 0; c < size; c += ECOMMUNITY_SIZE)
243: {
244: key += pnt[c];
245: key += pnt[c + 1];
246: key += pnt[c + 2];
247: key += pnt[c + 3];
248: key += pnt[c + 4];
249: key += pnt[c + 5];
250: key += pnt[c + 6];
251: key += pnt[c + 7];
252: }
1.1 misho 253:
254: return key;
255: }
256:
257: /* Compare two Extended Communities Attribute structure. */
258: int
259: ecommunity_cmp (const void *arg1, const void *arg2)
260: {
261: const struct ecommunity *ecom1 = arg1;
262: const struct ecommunity *ecom2 = arg2;
263:
264: return (ecom1->size == ecom2->size
265: && memcmp (ecom1->val, ecom2->val, ecom1->size * ECOMMUNITY_SIZE) == 0);
266: }
267:
268: /* Initialize Extended Comminities related hash. */
269: void
270: ecommunity_init (void)
271: {
272: ecomhash = hash_create (ecommunity_hash_make, ecommunity_cmp);
273: }
274:
275: void
276: ecommunity_finish (void)
277: {
278: hash_free (ecomhash);
279: ecomhash = NULL;
280: }
1.1.1.4 ! misho 281:
1.1 misho 282: /* Extended Communities token enum. */
283: enum ecommunity_token
284: {
1.1.1.4 ! misho 285: ecommunity_token_unknown = 0,
1.1 misho 286: ecommunity_token_rt,
287: ecommunity_token_soo,
288: ecommunity_token_val,
289: };
290:
291: /* Get next Extended Communities token from the string. */
292: static const char *
293: ecommunity_gettoken (const char *str, struct ecommunity_val *eval,
294: enum ecommunity_token *token)
295: {
296: int ret;
297: int dot = 0;
298: int digit = 0;
299: int separator = 0;
300: const char *p = str;
301: char *endptr;
302: struct in_addr ip;
303: as_t as = 0;
304: u_int32_t val = 0;
305: char buf[INET_ADDRSTRLEN + 1];
306:
307: /* Skip white space. */
308: while (isspace ((int) *p))
309: {
310: p++;
311: str++;
312: }
313:
314: /* Check the end of the line. */
315: if (*p == '\0')
316: return NULL;
317:
318: /* "rt" and "soo" keyword parse. */
319: if (! isdigit ((int) *p))
320: {
321: /* "rt" match check. */
322: if (tolower ((int) *p) == 'r')
323: {
324: p++;
325: if (tolower ((int) *p) == 't')
326: {
327: p++;
328: *token = ecommunity_token_rt;
329: return p;
330: }
331: if (isspace ((int) *p) || *p == '\0')
332: {
333: *token = ecommunity_token_rt;
334: return p;
335: }
336: goto error;
337: }
338: /* "soo" match check. */
339: else if (tolower ((int) *p) == 's')
340: {
341: p++;
342: if (tolower ((int) *p) == 'o')
343: {
344: p++;
345: if (tolower ((int) *p) == 'o')
346: {
347: p++;
348: *token = ecommunity_token_soo;
349: return p;
350: }
351: if (isspace ((int) *p) || *p == '\0')
352: {
353: *token = ecommunity_token_soo;
354: return p;
355: }
356: goto error;
357: }
358: if (isspace ((int) *p) || *p == '\0')
359: {
360: *token = ecommunity_token_soo;
361: return p;
362: }
363: goto error;
364: }
365: goto error;
366: }
367:
368: /* What a mess, there are several possibilities:
369: *
370: * a) A.B.C.D:MN
371: * b) EF:OPQR
372: * c) GHJK:MN
373: *
374: * A.B.C.D: Four Byte IP
375: * EF: Two byte ASN
376: * GHJK: Four-byte ASN
377: * MN: Two byte value
378: * OPQR: Four byte value
379: *
380: */
381: while (isdigit ((int) *p) || *p == ':' || *p == '.')
382: {
383: if (*p == ':')
384: {
385: if (separator)
386: goto error;
387:
388: separator = 1;
389: digit = 0;
390:
391: if ((p - str) > INET_ADDRSTRLEN)
392: goto error;
393: memset (buf, 0, INET_ADDRSTRLEN + 1);
394: memcpy (buf, str, p - str);
395:
396: if (dot)
397: {
398: /* Parsing A.B.C.D in:
399: * A.B.C.D:MN
400: */
401: ret = inet_aton (buf, &ip);
402: if (ret == 0)
403: goto error;
404: }
405: else
406: {
407: /* ASN */
408: as = strtoul (buf, &endptr, 10);
409: if (*endptr != '\0' || as == BGP_AS4_MAX)
410: goto error;
411: }
412: }
413: else if (*p == '.')
414: {
415: if (separator)
416: goto error;
417: dot++;
418: if (dot > 4)
419: goto error;
420: }
421: else
422: {
423: digit = 1;
424:
425: /* We're past the IP/ASN part */
426: if (separator)
427: {
428: val *= 10;
429: val += (*p - '0');
430: }
431: }
432: p++;
433: }
434:
435: /* Low digit part must be there. */
436: if (!digit || !separator)
437: goto error;
438:
439: /* Encode result into routing distinguisher. */
440: if (dot)
441: {
442: if (val > UINT16_MAX)
443: goto error;
444:
445: eval->val[0] = ECOMMUNITY_ENCODE_IP;
446: eval->val[1] = 0;
447: memcpy (&eval->val[2], &ip, sizeof (struct in_addr));
448: eval->val[6] = (val >> 8) & 0xff;
449: eval->val[7] = val & 0xff;
450: }
451: else if (as > BGP_AS_MAX)
452: {
453: if (val > UINT16_MAX)
454: goto error;
455:
456: eval->val[0] = ECOMMUNITY_ENCODE_AS4;
457: eval->val[1] = 0;
458: eval->val[2] = (as >>24) & 0xff;
459: eval->val[3] = (as >>16) & 0xff;
460: eval->val[4] = (as >>8) & 0xff;
461: eval->val[5] = as & 0xff;
462: eval->val[6] = (val >> 8) & 0xff;
463: eval->val[7] = val & 0xff;
464: }
465: else
466: {
467: eval->val[0] = ECOMMUNITY_ENCODE_AS;
468: eval->val[1] = 0;
469:
470: eval->val[2] = (as >>8) & 0xff;
471: eval->val[3] = as & 0xff;
472: eval->val[4] = (val >>24) & 0xff;
473: eval->val[5] = (val >>16) & 0xff;
474: eval->val[6] = (val >>8) & 0xff;
475: eval->val[7] = val & 0xff;
476: }
477: *token = ecommunity_token_val;
478: return p;
479:
480: error:
481: *token = ecommunity_token_unknown;
482: return p;
483: }
484:
485: /* Convert string to extended community attribute.
486:
487: When type is already known, please specify both str and type. str
488: should not include keyword such as "rt" and "soo". Type is
489: ECOMMUNITY_ROUTE_TARGET or ECOMMUNITY_SITE_ORIGIN.
490: keyword_included should be zero.
491:
492: For example route-map's "set extcommunity" command case:
493:
494: "rt 100:1 100:2 100:3" -> str = "100:1 100:2 100:3"
495: type = ECOMMUNITY_ROUTE_TARGET
496: keyword_included = 0
497:
498: "soo 100:1" -> str = "100:1"
499: type = ECOMMUNITY_SITE_ORIGIN
500: keyword_included = 0
501:
502: When string includes keyword for each extended community value.
503: Please specify keyword_included as non-zero value.
504:
505: For example standard extcommunity-list case:
506:
507: "rt 100:1 rt 100:2 soo 100:1" -> str = "rt 100:1 rt 100:2 soo 100:1"
508: type = 0
509: keyword_include = 1
510: */
511: struct ecommunity *
512: ecommunity_str2com (const char *str, int type, int keyword_included)
513: {
514: struct ecommunity *ecom = NULL;
1.1.1.4 ! misho 515: enum ecommunity_token token = ecommunity_token_unknown;
1.1 misho 516: struct ecommunity_val eval;
517: int keyword = 0;
518:
519: while ((str = ecommunity_gettoken (str, &eval, &token)))
520: {
521: switch (token)
522: {
523: case ecommunity_token_rt:
524: case ecommunity_token_soo:
525: if (! keyword_included || keyword)
526: {
527: if (ecom)
528: ecommunity_free (&ecom);
529: return NULL;
530: }
531: keyword = 1;
532:
533: if (token == ecommunity_token_rt)
534: {
535: type = ECOMMUNITY_ROUTE_TARGET;
536: }
537: if (token == ecommunity_token_soo)
538: {
539: type = ECOMMUNITY_SITE_ORIGIN;
540: }
541: break;
542: case ecommunity_token_val:
543: if (keyword_included)
544: {
545: if (! keyword)
546: {
547: if (ecom)
548: ecommunity_free (&ecom);
549: return NULL;
550: }
551: keyword = 0;
552: }
553: if (ecom == NULL)
554: ecom = ecommunity_new ();
555: eval.val[1] = type;
556: ecommunity_add_val (ecom, &eval);
557: break;
558: case ecommunity_token_unknown:
559: default:
560: if (ecom)
561: ecommunity_free (&ecom);
562: return NULL;
563: }
564: }
565: return ecom;
566: }
567:
568: /* Convert extended community attribute to string.
569:
570: Due to historical reason of industry standard implementation, there
571: are three types of format.
572:
573: route-map set extcommunity format
574: "rt 100:1 100:2"
575: "soo 100:3"
576:
577: extcommunity-list
578: "rt 100:1 rt 100:2 soo 100:3"
579:
580: "show ip bgp" and extcommunity-list regular expression matching
581: "RT:100:1 RT:100:2 SoO:100:3"
582:
583: For each formath please use below definition for format:
584:
585: ECOMMUNITY_FORMAT_ROUTE_MAP
586: ECOMMUNITY_FORMAT_COMMUNITY_LIST
587: ECOMMUNITY_FORMAT_DISPLAY
588: */
589: char *
590: ecommunity_ecom2str (struct ecommunity *ecom, int format)
591: {
592: int i;
593: u_int8_t *pnt;
594: int encode = 0;
595: int type = 0;
596: #define ECOMMUNITY_STR_DEFAULT_LEN 27
597: int str_size;
598: int str_pnt;
599: char *str_buf;
600: const char *prefix;
601: int len = 0;
602: int first = 1;
603:
604: /* For parse Extended Community attribute tupple. */
605: struct ecommunity_as
606: {
607: as_t as;
608: u_int32_t val;
609: } eas;
610:
611: struct ecommunity_ip
612: {
613: struct in_addr ip;
614: u_int16_t val;
615: } eip;
616:
617: if (ecom->size == 0)
618: {
619: str_buf = XMALLOC (MTYPE_ECOMMUNITY_STR, 1);
620: str_buf[0] = '\0';
621: return str_buf;
622: }
623:
624: /* Prepare buffer. */
625: str_buf = XMALLOC (MTYPE_ECOMMUNITY_STR, ECOMMUNITY_STR_DEFAULT_LEN + 1);
626: str_size = ECOMMUNITY_STR_DEFAULT_LEN + 1;
627: str_pnt = 0;
628:
629: for (i = 0; i < ecom->size; i++)
630: {
631: /* Make it sure size is enough. */
632: while (str_pnt + ECOMMUNITY_STR_DEFAULT_LEN >= str_size)
633: {
634: str_size *= 2;
635: str_buf = XREALLOC (MTYPE_ECOMMUNITY_STR, str_buf, str_size);
636: }
637:
638: /* Space between each value. */
639: if (! first)
640: str_buf[str_pnt++] = ' ';
641:
642: pnt = ecom->val + (i * 8);
643:
644: /* High-order octet of type. */
645: encode = *pnt++;
1.1.1.4 ! misho 646:
! 647: switch (encode)
! 648: {
! 649: case ECOMMUNITY_ENCODE_AS:
! 650: case ECOMMUNITY_ENCODE_IP:
! 651: case ECOMMUNITY_ENCODE_AS4:
! 652: break;
! 653:
! 654: case ECOMMUNITY_ENCODE_OPAQUE:
! 655: if (*pnt == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP)
! 656: {
! 657: uint16_t tunneltype;
! 658: memcpy (&tunneltype, pnt + 5, 2);
! 659: tunneltype = ntohs(tunneltype);
! 660: len = sprintf (str_buf + str_pnt, "ET:%d", tunneltype);
! 661: str_pnt += len;
! 662: first = 0;
! 663: continue;
! 664: }
! 665: /* fall through */
! 666:
! 667: default:
! 668: len = sprintf (str_buf + str_pnt, "?");
! 669: str_pnt += len;
! 670: first = 0;
! 671: continue;
! 672: }
! 673:
1.1 misho 674: /* Low-order octet of type. */
675: type = *pnt++;
676: if (type != ECOMMUNITY_ROUTE_TARGET && type != ECOMMUNITY_SITE_ORIGIN)
677: {
678: len = sprintf (str_buf + str_pnt, "?");
679: str_pnt += len;
680: first = 0;
681: continue;
682: }
683:
684: switch (format)
685: {
686: case ECOMMUNITY_FORMAT_COMMUNITY_LIST:
687: prefix = (type == ECOMMUNITY_ROUTE_TARGET ? "rt " : "soo ");
688: break;
689: case ECOMMUNITY_FORMAT_DISPLAY:
690: prefix = (type == ECOMMUNITY_ROUTE_TARGET ? "RT:" : "SoO:");
691: break;
692: case ECOMMUNITY_FORMAT_ROUTE_MAP:
693: prefix = "";
694: break;
695: default:
696: prefix = "";
697: break;
698: }
699:
700: /* Put string into buffer. */
701: if (encode == ECOMMUNITY_ENCODE_AS4)
702: {
703: eas.as = (*pnt++ << 24);
704: eas.as |= (*pnt++ << 16);
705: eas.as |= (*pnt++ << 8);
706: eas.as |= (*pnt++);
707:
708: eas.val = (*pnt++ << 8);
709: eas.val |= (*pnt++);
710:
1.1.1.4 ! misho 711: len = sprintf( str_buf + str_pnt, "%s%u:%u", prefix,
1.1 misho 712: eas.as, eas.val );
713: str_pnt += len;
714: first = 0;
715: }
716: if (encode == ECOMMUNITY_ENCODE_AS)
717: {
718: eas.as = (*pnt++ << 8);
719: eas.as |= (*pnt++);
720:
721: eas.val = (*pnt++ << 24);
722: eas.val |= (*pnt++ << 16);
723: eas.val |= (*pnt++ << 8);
724: eas.val |= (*pnt++);
725:
1.1.1.4 ! misho 726: len = sprintf (str_buf + str_pnt, "%s%u:%u", prefix,
1.1 misho 727: eas.as, eas.val);
728: str_pnt += len;
729: first = 0;
730: }
731: else if (encode == ECOMMUNITY_ENCODE_IP)
732: {
733: memcpy (&eip.ip, pnt, 4);
734: pnt += 4;
735: eip.val = (*pnt++ << 8);
736: eip.val |= (*pnt++);
737:
1.1.1.4 ! misho 738: len = sprintf (str_buf + str_pnt, "%s%s:%u", prefix,
1.1 misho 739: inet_ntoa (eip.ip), eip.val);
740: str_pnt += len;
741: first = 0;
742: }
743: }
744: return str_buf;
745: }
746:
747: int
748: ecommunity_match (const struct ecommunity *ecom1,
749: const struct ecommunity *ecom2)
750: {
751: int i = 0;
752: int j = 0;
753:
754: if (ecom1 == NULL && ecom2 == NULL)
755: return 1;
756:
757: if (ecom1 == NULL || ecom2 == NULL)
758: return 0;
759:
760: if (ecom1->size < ecom2->size)
761: return 0;
762:
763: /* Every community on com2 needs to be on com1 for this to match */
764: while (i < ecom1->size && j < ecom2->size)
765: {
766: if (memcmp (ecom1->val + i, ecom2->val + j, ECOMMUNITY_SIZE) == 0)
767: j++;
768: i++;
769: }
770:
771: if (j == ecom2->size)
772: return 1;
773: else
774: return 0;
775: }
776:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>