]> git.ipfire.org Git - thirdparty/bird.git/blob - nest/rt-attr.c
Fixes signedness in format route attributes.
[thirdparty/bird.git] / nest / rt-attr.c
1 /*
2 * BIRD -- Route Attribute Cache
3 *
4 * (c) 1998--2000 Martin Mares <mj@ucw.cz>
5 *
6 * Can be freely distributed and used under the terms of the GNU GPL.
7 */
8
9 /**
10 * DOC: Route attribute cache
11 *
12 * Each route entry carries a set of route attributes. Several of them
13 * vary from route to route, but most attributes are usually common
14 * for a large number of routes. To conserve memory, we've decided to
15 * store only the varying ones directly in the &rte and hold the rest
16 * in a special structure called &rta which is shared among all the
17 * &rte's with these attributes.
18 *
19 * Each &rta contains all the static attributes of the route (i.e.,
20 * those which are always present) as structure members and a list of
21 * dynamic attributes represented by a linked list of &ea_list
22 * structures, each of them consisting of an array of &eattr's containing
23 * the individual attributes. An attribute can be specified more than once
24 * in the &ea_list chain and in such case the first occurrence overrides
25 * the others. This semantics is used especially when someone (for example
26 * a filter) wishes to alter values of several dynamic attributes, but
27 * it wants to preserve the original attribute lists maintained by
28 * another module.
29 *
30 * Each &eattr contains an attribute identifier (split to protocol ID and
31 * per-protocol attribute ID), protocol dependent flags, a type code (consisting
32 * of several bit fields describing attribute characteristics) and either an
33 * embedded 32-bit value or a pointer to a &adata structure holding attribute
34 * contents.
35 *
36 * There exist two variants of &rta's -- cached and un-cached ones. Un-cached
37 * &rta's can have arbitrarily complex structure of &ea_list's and they
38 * can be modified by any module in the route processing chain. Cached
39 * &rta's have their attribute lists normalized (that means at most one
40 * &ea_list is present and its values are sorted in order to speed up
41 * searching), they are stored in a hash table to make fast lookup possible
42 * and they are provided with a use count to allow sharing.
43 *
44 * Routing tables always contain only cached &rta's.
45 */
46
47 #include "nest/bird.h"
48 #include "nest/route.h"
49 #include "nest/protocol.h"
50 #include "nest/iface.h"
51 #include "nest/cli.h"
52 #include "nest/attrs.h"
53 #include "lib/alloca.h"
54 #include "lib/resource.h"
55 #include "lib/string.h"
56
57 static slab *rta_slab;
58 static pool *rta_pool;
59
60 struct protocol *attr_class_to_protocol[EAP_MAX];
61
62 /*
63 * Extended Attributes
64 */
65
66 static inline eattr *
67 ea__find(ea_list *e, unsigned id)
68 {
69 eattr *a;
70 int l, r, m;
71
72 while (e)
73 {
74 if (e->flags & EALF_BISECT)
75 {
76 l = 0;
77 r = e->count - 1;
78 while (l <= r)
79 {
80 m = (l+r) / 2;
81 a = &e->attrs[m];
82 if (a->id == id)
83 return a;
84 else if (a->id < id)
85 l = m+1;
86 else
87 r = m-1;
88 }
89 }
90 else
91 for(m=0; m<e->count; m++)
92 if (e->attrs[m].id == id)
93 return &e->attrs[m];
94 e = e->next;
95 }
96 return NULL;
97 }
98
99 /**
100 * ea_find - find an extended attribute
101 * @e: attribute list to search in
102 * @id: attribute ID to search for
103 *
104 * Given an extended attribute list, ea_find() searches for a first
105 * occurrence of an attribute with specified ID, returning either a pointer
106 * to its &eattr structure or %NULL if no such attribute exists.
107 */
108 eattr *
109 ea_find(ea_list *e, unsigned id)
110 {
111 eattr *a = ea__find(e, id & EA_CODE_MASK);
112
113 if (a && (a->type & EAF_TYPE_MASK) == EAF_TYPE_UNDEF &&
114 !(id & EA_ALLOW_UNDEF))
115 return NULL;
116 return a;
117 }
118
119 /**
120 * ea_get_int - fetch an integer attribute
121 * @e: attribute list
122 * @id: attribute ID
123 * @def: default value
124 *
125 * This function is a shortcut for retrieving a value of an integer attribute
126 * by calling ea_find() to find the attribute, extracting its value or returning
127 * a provided default if no such attribute is present.
128 */
129 int
130 ea_get_int(ea_list *e, unsigned id, int def)
131 {
132 eattr *a = ea_find(e, id);
133 if (!a)
134 return def;
135 return a->u.data;
136 }
137
138 static inline void
139 ea_do_sort(ea_list *e)
140 {
141 unsigned n = e->count;
142 eattr *a = e->attrs;
143 eattr *b = alloca(n * sizeof(eattr));
144 unsigned s, ss;
145
146 /* We need to use a stable sorting algorithm, hence mergesort */
147 do
148 {
149 s = ss = 0;
150 while (s < n)
151 {
152 eattr *p, *q, *lo, *hi;
153 p = b;
154 ss = s;
155 *p++ = a[s++];
156 while (s < n && p[-1].id <= a[s].id)
157 *p++ = a[s++];
158 if (s < n)
159 {
160 q = p;
161 *p++ = a[s++];
162 while (s < n && p[-1].id <= a[s].id)
163 *p++ = a[s++];
164 lo = b;
165 hi = q;
166 s = ss;
167 while (lo < q && hi < p)
168 if (lo->id <= hi->id)
169 a[s++] = *lo++;
170 else
171 a[s++] = *hi++;
172 while (lo < q)
173 a[s++] = *lo++;
174 while (hi < p)
175 a[s++] = *hi++;
176 }
177 }
178 }
179 while (ss);
180 }
181
182 static inline void
183 ea_do_prune(ea_list *e)
184 {
185 eattr *s, *d, *l, *s0;
186 int i = 0;
187
188 /* Discard duplicates and undefs. Do you remember sorting was stable? */
189 s = d = e->attrs;
190 l = e->attrs + e->count;
191 while (s < l)
192 {
193 s0 = s++;
194 while (s < l && s->id == s[-1].id)
195 s++;
196 /* s0 is the most recent version, s[-1] the oldest one */
197 if ((s0->type & EAF_TYPE_MASK) != EAF_TYPE_UNDEF)
198 {
199 *d = *s0;
200 d->type = (d->type & ~EAF_ORIGINATED) | (s[-1].type & EAF_ORIGINATED);
201 d++;
202 i++;
203 }
204 }
205 e->count = i;
206 }
207
208 /**
209 * ea_sort - sort an attribute list
210 * @e: list to be sorted
211 *
212 * This function takes a &ea_list chain and sorts the attributes
213 * within each of its entries.
214 *
215 * If an attribute occurs multiple times in a single &ea_list,
216 * ea_sort() leaves only the first (the only significant) occurrence.
217 */
218 void
219 ea_sort(ea_list *e)
220 {
221 while (e)
222 {
223 if (!(e->flags & EALF_SORTED))
224 {
225 ea_do_sort(e);
226 ea_do_prune(e);
227 e->flags |= EALF_SORTED;
228 }
229 if (e->count > 5)
230 e->flags |= EALF_BISECT;
231 e = e->next;
232 }
233 }
234
235 /**
236 * ea_scan - estimate attribute list size
237 * @e: attribute list
238 *
239 * This function calculates an upper bound of the size of
240 * a given &ea_list after merging with ea_merge().
241 */
242 unsigned
243 ea_scan(ea_list *e)
244 {
245 unsigned cnt = 0;
246
247 while (e)
248 {
249 cnt += e->count;
250 e = e->next;
251 }
252 return sizeof(ea_list) + sizeof(eattr)*cnt;
253 }
254
255 /**
256 * ea_merge - merge segments of an attribute list
257 * @e: attribute list
258 * @t: buffer to store the result to
259 *
260 * This function takes a possibly multi-segment attribute list
261 * and merges all of its segments to one.
262 *
263 * The primary use of this function is for &ea_list normalization:
264 * first call ea_scan() to determine how much memory will the result
265 * take, then allocate a buffer (usually using alloca()), merge the
266 * segments with ea_merge() and finally sort and prune the result
267 * by calling ea_sort().
268 */
269 void
270 ea_merge(ea_list *e, ea_list *t)
271 {
272 eattr *d = t->attrs;
273
274 t->flags = 0;
275 t->count = 0;
276 t->next = NULL;
277 while (e)
278 {
279 memcpy(d, e->attrs, sizeof(eattr)*e->count);
280 t->count += e->count;
281 d += e->count;
282 e = e->next;
283 }
284 }
285
286 /**
287 * ea_same - compare two &ea_list's
288 * @x: attribute list
289 * @y: attribute list
290 *
291 * ea_same() compares two normalized attribute lists @x and @y and returns
292 * 1 if they contain the same attributes, 0 otherwise.
293 */
294 int
295 ea_same(ea_list *x, ea_list *y)
296 {
297 int c;
298
299 if (!x || !y)
300 return x == y;
301 ASSERT(!x->next && !y->next);
302 if (x->count != y->count)
303 return 0;
304 for(c=0; c<x->count; c++)
305 {
306 eattr *a = &x->attrs[c];
307 eattr *b = &y->attrs[c];
308
309 if (a->id != b->id ||
310 a->flags != b->flags ||
311 a->type != b->type ||
312 ((a->type & EAF_EMBEDDED) ? a->u.data != b->u.data :
313 (a->u.ptr->length != b->u.ptr->length || memcmp(a->u.ptr->data, b->u.ptr->data, a->u.ptr->length))))
314 return 0;
315 }
316 return 1;
317 }
318
319 static inline ea_list *
320 ea_list_copy(ea_list *o)
321 {
322 ea_list *n;
323 unsigned i, len;
324
325 if (!o)
326 return NULL;
327 ASSERT(!o->next);
328 len = sizeof(ea_list) + sizeof(eattr) * o->count;
329 n = mb_alloc(rta_pool, len);
330 memcpy(n, o, len);
331 n->flags |= EALF_CACHED;
332 for(i=0; i<o->count; i++)
333 {
334 eattr *a = &n->attrs[i];
335 if (!(a->type & EAF_EMBEDDED))
336 {
337 unsigned size = sizeof(struct adata) + a->u.ptr->length;
338 struct adata *d = mb_alloc(rta_pool, size);
339 memcpy(d, a->u.ptr, size);
340 a->u.ptr = d;
341 }
342 }
343 return n;
344 }
345
346 static inline void
347 ea_free(ea_list *o)
348 {
349 int i;
350
351 if (o)
352 {
353 ASSERT(!o->next);
354 for(i=0; i<o->count; i++)
355 {
356 eattr *a = &o->attrs[i];
357 if (!(a->type & EAF_EMBEDDED))
358 mb_free(a->u.ptr);
359 }
360 mb_free(o);
361 }
362 }
363
364 /**
365 * ea_format - format an &eattr for printing
366 * @e: attribute to be formatted
367 * @buf: destination buffer of size %EA_FORMAT_BUF_SIZE
368 *
369 * This function takes an extended attribute represented by its
370 * &eattr structure and formats it nicely for printing according
371 * to the type information.
372 *
373 * If the protocol defining the attribute provides its own
374 * get_attr() hook, it's consulted first.
375 */
376 void
377 ea_format(eattr *e, byte *buf)
378 {
379 struct protocol *p;
380 int status = GA_UNKNOWN;
381 unsigned int i;
382 struct adata *ad = (e->type & EAF_EMBEDDED) ? NULL : e->u.ptr;
383 byte *end = buf + EA_FORMAT_BUF_SIZE - 1;
384
385 if (p = attr_class_to_protocol[EA_PROTO(e->id)])
386 {
387 buf += bsprintf(buf, "%s.", p->name);
388 if (p->get_attr)
389 status = p->get_attr(e, buf, end - buf);
390 buf += strlen(buf);
391 }
392 else if (EA_PROTO(e->id))
393 buf += bsprintf(buf, "%02x.", EA_PROTO(e->id));
394 if (status < GA_NAME)
395 buf += bsprintf(buf, "%02x", EA_ID(e->id));
396 if (status < GA_FULL)
397 {
398 *buf++ = ':';
399 *buf++ = ' ';
400 switch (e->type & EAF_TYPE_MASK)
401 {
402 case EAF_TYPE_INT:
403 bsprintf(buf, "%u", e->u.data);
404 break;
405 case EAF_TYPE_OPAQUE:
406 for(i=0; i<ad->length; i++)
407 {
408 if (buf > end - 8)
409 {
410 strcpy(buf, " ...");
411 break;
412 }
413 if (i)
414 *buf++ = ' ';
415 buf += bsprintf(buf, "%02x", ad->data[i]);
416 }
417 break;
418 case EAF_TYPE_IP_ADDRESS:
419 bsprintf(buf, "%I", *(ip_addr *) ad->data);
420 break;
421 case EAF_TYPE_ROUTER_ID:
422 bsprintf(buf, "%R", e->u.data);
423 break;
424 case EAF_TYPE_AS_PATH:
425 as_path_format(ad, buf, end - buf);
426 break;
427 case EAF_TYPE_INT_SET:
428 int_set_format(ad, 1, buf, end - buf);
429 break;
430 case EAF_TYPE_UNDEF:
431 default:
432 bsprintf(buf, "<type %02x>", e->type);
433 }
434 }
435 }
436
437 /**
438 * ea_dump - dump an extended attribute
439 * @e: attribute to be dumped
440 *
441 * ea_dump() dumps contents of the extended attribute given to
442 * the debug output.
443 */
444 void
445 ea_dump(ea_list *e)
446 {
447 int i;
448
449 if (!e)
450 {
451 debug("NONE");
452 return;
453 }
454 while (e)
455 {
456 debug("[%c%c%c]",
457 (e->flags & EALF_SORTED) ? 'S' : 's',
458 (e->flags & EALF_BISECT) ? 'B' : 'b',
459 (e->flags & EALF_CACHED) ? 'C' : 'c');
460 for(i=0; i<e->count; i++)
461 {
462 eattr *a = &e->attrs[i];
463 debug(" %02x:%02x.%02x", EA_PROTO(a->id), EA_ID(a->id), a->flags);
464 if (a->type & EAF_TEMP)
465 debug("T");
466 debug("=%c", "?iO?I?P???S?????" [a->type & EAF_TYPE_MASK]);
467 if (a->type & EAF_ORIGINATED)
468 debug("o");
469 if (a->type & EAF_EMBEDDED)
470 debug(":%08x", a->u.data);
471 else
472 {
473 int j, len = a->u.ptr->length;
474 debug("[%d]:", len);
475 for(j=0; j<len; j++)
476 debug("%02x", a->u.ptr->data[j]);
477 }
478 }
479 if (e = e->next)
480 debug(" | ");
481 }
482 }
483
484 /**
485 * ea_hash - calculate an &ea_list hash key
486 * @e: attribute list
487 *
488 * ea_hash() takes an extended attribute list and calculated a hopefully
489 * uniformly distributed hash value from its contents.
490 */
491 inline unsigned int
492 ea_hash(ea_list *e)
493 {
494 u32 h = 0;
495 int i;
496
497 if (e) /* Assuming chain of length 1 */
498 {
499 for(i=0; i<e->count; i++)
500 {
501 struct eattr *a = &e->attrs[i];
502 h ^= a->id;
503 if (a->type & EAF_EMBEDDED)
504 h ^= a->u.data;
505 else
506 {
507 struct adata *d = a->u.ptr;
508 int size = d->length;
509 byte *z = d->data;
510 while (size >= 4)
511 {
512 h ^= *(u32 *)z;
513 z += 4;
514 size -= 4;
515 }
516 while (size--)
517 h = (h >> 24) ^ (h << 8) ^ *z++;
518 }
519 }
520 h ^= h >> 16;
521 h ^= h >> 6;
522 h &= 0xffff;
523 }
524 return h;
525 }
526
527 /**
528 * ea_append - concatenate &ea_list's
529 * @to: destination list (can be %NULL)
530 * @what: list to be appended (can be %NULL)
531 *
532 * This function appends the &ea_list @what at the end of
533 * &ea_list @to and returns a pointer to the resulting list.
534 */
535 ea_list *
536 ea_append(ea_list *to, ea_list *what)
537 {
538 ea_list *res;
539
540 if (!to)
541 return what;
542 res = to;
543 while (to->next)
544 to = to->next;
545 to->next = what;
546 return res;
547 }
548
549 /*
550 * rta's
551 */
552
553 static unsigned int rta_cache_count;
554 static unsigned int rta_cache_size = 32;
555 static unsigned int rta_cache_limit;
556 static unsigned int rta_cache_mask;
557 static rta **rta_hash_table;
558
559 static void
560 rta_alloc_hash(void)
561 {
562 rta_hash_table = mb_allocz(rta_pool, sizeof(rta *) * rta_cache_size);
563 if (rta_cache_size < 32768)
564 rta_cache_limit = rta_cache_size * 2;
565 else
566 rta_cache_limit = ~0;
567 rta_cache_mask = rta_cache_size - 1;
568 }
569
570 static inline unsigned int
571 rta_hash(rta *a)
572 {
573 return (a->proto->hash_key ^ ipa_hash(a->gw) ^ ea_hash(a->eattrs)) & 0xffff;
574 }
575
576 static inline int
577 rta_same(rta *x, rta *y)
578 {
579 return (x->proto == y->proto &&
580 x->source == y->source &&
581 x->scope == y->scope &&
582 x->cast == y->cast &&
583 x->dest == y->dest &&
584 x->flags == y->flags &&
585 ipa_equal(x->gw, y->gw) &&
586 ipa_equal(x->from, y->from) &&
587 x->iface == y->iface &&
588 ea_same(x->eattrs, y->eattrs));
589 }
590
591 static rta *
592 rta_copy(rta *o)
593 {
594 rta *r = sl_alloc(rta_slab);
595
596 memcpy(r, o, sizeof(rta));
597 r->uc = 1;
598 r->eattrs = ea_list_copy(o->eattrs);
599 return r;
600 }
601
602 static inline void
603 rta_insert(rta *r)
604 {
605 unsigned int h = r->hash_key & rta_cache_mask;
606 r->next = rta_hash_table[h];
607 if (r->next)
608 r->next->pprev = &r->next;
609 r->pprev = &rta_hash_table[h];
610 rta_hash_table[h] = r;
611 }
612
613 static void
614 rta_rehash(void)
615 {
616 unsigned int ohs = rta_cache_size;
617 unsigned int h;
618 rta *r, *n;
619 rta **oht = rta_hash_table;
620
621 rta_cache_size = 2*rta_cache_size;
622 DBG("Rehashing rta cache from %d to %d entries.\n", ohs, rta_cache_size);
623 rta_alloc_hash();
624 for(h=0; h<ohs; h++)
625 for(r=oht[h]; r; r=n)
626 {
627 n = r->next;
628 rta_insert(r);
629 }
630 mb_free(oht);
631 }
632
633 /**
634 * rta_lookup - look up a &rta in attribute cache
635 * @o: a un-cached &rta
636 *
637 * rta_lookup() gets an un-cached &rta structure and returns its cached
638 * counterpart. It starts with examining the attribute cache to see whether
639 * there exists a matching entry. If such an entry exists, it's returned and
640 * its use count is incremented, else a new entry is created with use count
641 * set to 1.
642 *
643 * The extended attribute lists attached to the &rta are automatically
644 * converted to the normalized form.
645 */
646 rta *
647 rta_lookup(rta *o)
648 {
649 rta *r;
650 unsigned int h;
651
652 ASSERT(!(o->aflags & RTAF_CACHED));
653 if (o->eattrs)
654 {
655 if (o->eattrs->next) /* Multiple ea_list's, need to merge them */
656 {
657 ea_list *ml = alloca(ea_scan(o->eattrs));
658 ea_merge(o->eattrs, ml);
659 o->eattrs = ml;
660 }
661 ea_sort(o->eattrs);
662 }
663
664 h = rta_hash(o);
665 for(r=rta_hash_table[h & rta_cache_mask]; r; r=r->next)
666 if (r->hash_key == h && rta_same(r, o))
667 return rta_clone(r);
668
669 r = rta_copy(o);
670 r->hash_key = h;
671 r->aflags = RTAF_CACHED;
672 rta_insert(r);
673
674 if (++rta_cache_count > rta_cache_limit)
675 rta_rehash();
676
677 return r;
678 }
679
680 void
681 rta__free(rta *a)
682 {
683 ASSERT(rta_cache_count && (a->aflags & RTAF_CACHED));
684 rta_cache_count--;
685 *a->pprev = a->next;
686 if (a->next)
687 a->next->pprev = a->pprev;
688 a->aflags = 0; /* Poison the entry */
689 ea_free(a->eattrs);
690 sl_free(rta_slab, a);
691 }
692
693 /**
694 * rta_dump - dump route attributes
695 * @a: attribute structure to dump
696 *
697 * This function takes a &rta and dumps its contents to the debug output.
698 */
699 void
700 rta_dump(rta *a)
701 {
702 static char *rts[] = { "RTS_DUMMY", "RTS_STATIC", "RTS_INHERIT", "RTS_DEVICE",
703 "RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP",
704 "RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1",
705 "RTS_OSPF_EXT2", "RTS_BGP" };
706 static char *rtc[] = { "", " BC", " MC", " AC" };
707 static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" };
708
709 debug("p=%s uc=%d %s %s%s%s h=%04x",
710 a->proto->name, a->uc, rts[a->source], ip_scope_text(a->scope), rtc[a->cast],
711 rtd[a->dest], a->hash_key);
712 if (!(a->aflags & RTAF_CACHED))
713 debug(" !CACHED");
714 debug(" <-%I", a->from);
715 if (a->dest == RTD_ROUTER)
716 debug(" ->%I", a->gw);
717 if (a->dest == RTD_DEVICE || a->dest == RTD_ROUTER)
718 debug(" [%s]", a->iface ? a->iface->name : "???" );
719 if (a->eattrs)
720 {
721 debug(" EA: ");
722 ea_dump(a->eattrs);
723 }
724 }
725
726 /**
727 * rta_dump_all - dump attribute cache
728 *
729 * This function dumps the whole contents of route attribute cache
730 * to the debug output.
731 */
732 void
733 rta_dump_all(void)
734 {
735 rta *a;
736 unsigned int h;
737
738 debug("Route attribute cache (%d entries, rehash at %d):\n", rta_cache_count, rta_cache_limit);
739 for(h=0; h<rta_cache_size; h++)
740 for(a=rta_hash_table[h]; a; a=a->next)
741 {
742 debug("%p ", a);
743 rta_dump(a);
744 debug("\n");
745 }
746 debug("\n");
747 }
748
749 void
750 rta_show(struct cli *c, rta *a, ea_list *eal)
751 {
752 static char *src_names[] = { "dummy", "static", "inherit", "device", "static-device", "redirect",
753 "RIP", "OSPF", "OSPF-ext", "OSPF-IA", "OSPF-boundary", "BGP" };
754 static char *cast_names[] = { "unicast", "broadcast", "multicast", "anycast" };
755 int i;
756 byte buf[EA_FORMAT_BUF_SIZE];
757
758 cli_printf(c, -1008, "\tType: %s %s %s", src_names[a->source], cast_names[a->cast], ip_scope_text(a->scope));
759 if (!eal)
760 eal = a->eattrs;
761 for(; eal; eal=eal->next)
762 for(i=0; i<eal->count; i++)
763 {
764 ea_format(&eal->attrs[i], buf);
765 cli_printf(c, -1012, "\t%s", buf);
766 }
767 }
768
769 /**
770 * rta_init - initialize route attribute cache
771 *
772 * This function is called during initialization of the routing
773 * table module to set up the internals of the attribute cache.
774 */
775 void
776 rta_init(void)
777 {
778 rta_pool = rp_new(&root_pool, "Attributes");
779 rta_slab = sl_new(rta_pool, sizeof(rta));
780 rta_alloc_hash();
781 }
782
783 /*
784 * Documentation for functions declared inline in route.h
785 */
786 #if 0
787
788 /**
789 * rta_clone - clone route attributes
790 * @r: a &rta to be cloned
791 *
792 * rta_clone() takes a cached &rta and returns its identical cached
793 * copy. Currently it works by just returning the original &rta with
794 * its use count incremented.
795 */
796 static inline rta *rta_clone(rta *r)
797 { DUMMY; }
798
799 /**
800 * rta_free - free route attributes
801 * @r: a &rta to be freed
802 *
803 * If you stop using a &rta (for example when deleting a route which uses
804 * it), you need to call rta_free() to notify the attribute cache the
805 * attribute is no longer in use and can be freed if you were the last
806 * user (which rta_free() tests by inspecting the use count).
807 */
808 static inline void rta_free(rta *r)
809 { DUMMY; }
810
811 #endif