rt-table.c 16.6 KB
Newer Older
1 2 3
/*
 *	BIRD -- Routing Table
 *
4
 *	(c) 1998--2000 Martin Mares <mj@ucw.cz>
5 6 7 8
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

9
#undef LOCAL_DEBUG
10

11 12
#include "nest/bird.h"
#include "nest/route.h"
13
#include "nest/protocol.h"
14 15
#include "nest/cli.h"
#include "nest/iface.h"
16
#include "lib/resource.h"
17
#include "lib/event.h"
18
#include "lib/string.h"
19
#include "conf/conf.h"
20
#include "filter/filter.h"
21
#include "lib/string.h"
22 23

static slab *rte_slab;
24
static linpool *rte_update_pool;
25

26
static pool *rt_table_pool;
27
static list routing_tables;
28

29 30
static void rt_format_via(rte *e, byte *via);

31
static void
32 33 34 35
rte_init(struct fib_node *N)
{
  net *n = (net *) N;

36
  N->flags = 0;
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
  n->routes = NULL;
}

rte *
rte_find(net *net, struct proto *p)
{
  rte *e = net->routes;

  while (e && e->attrs->proto != p)
    e = e->next;
  return e;
}

rte *
rte_get_temp(rta *a)
{
  rte *e = sl_alloc(rte_slab);

  e->attrs = a;
56
  e->flags = 0;
57 58 59 60
  e->pref = a->proto->preference;
  return e;
}

61 62 63 64 65 66 67 68 69 70 71
rte *
rte_do_cow(rte *r)
{
  rte *e = sl_alloc(rte_slab);

  memcpy(e, r, sizeof(rte));
  e->attrs = rta_clone(r->attrs);
  e->flags = 0;
  return e;
}

72 73 74
static int				/* Actually better or at least as good as */
rte_better(rte *new, rte *old)
{
75 76
  int (*better)(rte *, rte *);

77 78 79 80 81 82
  if (!old)
    return 1;
  if (new->pref > old->pref)
    return 1;
  if (new->pref < old->pref)
    return 0;
83
  if (new->attrs->proto->proto != old->attrs->proto->proto)
84 85 86 87 88 89 90 91
    {
      /*
       *  If the user has configured protocol preferences, so that two different protocols
       *  have the same preference, try to break the tie by comparing addresses. Not too
       *  useful, but keeps the ordering of routes unambiguous.
       */
      return new->attrs->proto->proto > old->attrs->proto->proto;
    }
92 93 94
  if (better = new->attrs->proto->rte_better)
    return better(new, old);
  return 0;
95 96
}

97 98 99 100 101 102 103 104 105 106 107 108 109
static void
rte_trace(struct proto *p, rte *e, int dir, char *msg)
{
  byte via[STD_ADDRESS_P_LENGTH+32];

  rt_format_via(e, via);
  log(L_TRACE "%s %c %s %I/%d %s", p->name, dir, msg, e->net->n.prefix, e->net->n.pxlen, via);
}

static inline void
rte_trace_in(unsigned int flag, struct proto *p, rte *e, char *msg)
{
  if (p->debug & flag)
110
    rte_trace(p, e, '>', msg);
111 112 113 114 115 116
}

static inline void
rte_trace_out(unsigned int flag, struct proto *p, rte *e, char *msg)
{
  if (p->debug & flag)
117
    rte_trace(p, e, '<', msg);
118 119
}

120
static inline void
121
do_rte_announce(struct announce_hook *a, net *net, rte *new, rte *old, ea_list *tmpa, int class)
122
{
123
  struct proto *p = a->proto;
124 125
  rte *new0 = new;
  rte *old0 = old;
126

127
  if (new)
128
    {
129
      int ok;
130 131 132 133 134 135 136 137 138 139 140 141 142
      if ((class & IADDR_SCOPE_MASK) < p->min_scope)
	{
	  rte_trace_out(D_FILTERS, p, new, "out of scope");
	  new = NULL;
	}
      else if ((ok = p->import_control ? p->import_control(p, &new, &tmpa, rte_update_pool) : 0) < 0)
	{
	  rte_trace_out(D_FILTERS, p, new, "rejected by protocol");
	  new = NULL;
	}
      else if (ok)
	rte_trace_out(D_FILTERS, p, new, "forced accept by protocol");
      else if (p->out_filter == FILTER_REJECT ||
143
	       p->out_filter && f_run(p->out_filter, &new, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)
144 145 146 147
	{
	  rte_trace_out(D_FILTERS, p, new, "filtered out");
	  new = NULL;
	}
148
    }
149 150 151 152 153 154 155 156
  if (old && p->out_filter)
    {
      /* FIXME: Do we really need to filter old routes? */
      if (p->out_filter == FILTER_REJECT)
	old = NULL;
      else
	{
	  ea_list *tmpb = p->make_tmp_attrs ? p->make_tmp_attrs(old, rte_update_pool) : NULL;
157
	  if (f_run(p->out_filter, &old, &tmpb, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)
158 159 160
	    old = NULL;
	}
    }
161 162 163 164 165 166
  if (p->debug & D_ROUTES)
    {
      if (new && old)
	rte_trace_out(D_ROUTES, p, new, "replaced");
      else if (new)
	rte_trace_out(D_ROUTES, p, new, "added");
167
      else if (old)
168 169
	rte_trace_out(D_ROUTES, p, old, "removed");
    }
170
  if (new || old)
171
    p->rt_notify(p, net, new, old, tmpa);
172 173 174 175
  if (new && new != new0)	/* Discard temporary rte's */
    rte_free(new);
  if (old && old != old0)
    rte_free(old);
176 177
}

178
static void
179
rte_announce(rtable *tab, net *net, rte *new, rte *old, ea_list *tmpa)
180
{
181
  struct announce_hook *a;
182
  int class = ipa_classify(net->n.prefix);
183

184
  WALK_LIST(a, tab->hooks)
185
    {
186
      ASSERT(a->proto->core_state == FS_HAPPY || a->proto->core_state == FS_FEEDING);
187
      do_rte_announce(a, net, new, old, tmpa, class);
188
    }
189 190
}

191 192 193
void
rt_feed_baby(struct proto *p)
{
194
  struct announce_hook *h;
195

196
  if (!p->ahooks)
197
    return;
198
  DBG("Announcing routes to new protocol %s\n", p->name);
199
  for(h=p->ahooks; h; h=h->next)
200
    {
201 202
      rtable *t = h->table;
      FIB_WALK(&t->fib, fn)
203
	{
204 205 206 207 208 209
	  net *n = (net *) fn;
	  rte *e;
	  for(e=n->routes; e; e=e->next)
	    {
	      struct proto *q = e->attrs->proto;
	      ea_list *tmpa = q->make_tmp_attrs ? q->make_tmp_attrs(e, rte_update_pool) : NULL;
210
	      do_rte_announce(h, n, e, NULL, tmpa, ipa_classify(n->n.prefix));
211 212
	      lp_flush(rte_update_pool);
	    }
213
	}
214
      FIB_WALK_END;
215 216 217
    }
}

218 219 220 221 222 223
static inline int
rte_validate(rte *e)
{
  int c;
  net *n = e->net;

224 225 226 227 228 229
  if (ipa_nonzero(ipa_and(n->n.prefix, ipa_not(ipa_mkmask(n->n.pxlen)))))
    {
      log(L_BUG "Ignoring bogus prefix %I/%d received via %s",
	  n->n.prefix, n->n.pxlen, e->attrs->proto->name);
      return 0;
    }
230 231 232 233 234
  if (n->n.pxlen)
    {
      c = ipa_classify(n->n.prefix);
      if (c < 0 || !(c & IADDR_HOST))
	{
235 236 237 238 239 240 241 242 243 244 245 246
	  if (!ipa_nonzero(n->n.prefix))
	    {
	      /* Various default routes */
#ifdef IPV6
	      if (n->n.pxlen == 96)
#else
	      if (n->n.pxlen <= 1)
#endif
		return 1;
	    }
	  log(L_WARN "Ignoring bogus route %I/%d received via %s",
	      n->n.prefix, n->n.pxlen, e->attrs->proto->name);
247 248
	  return 0;
	}
249
      if ((c & IADDR_SCOPE_MASK) < e->attrs->proto->min_scope)
250
	{
251 252 253 254
	  log(L_WARN "Ignoring %s scope route %I/%d received from %I via %s",
	      ip_scope_text(c & IADDR_SCOPE_MASK),
	      n->n.prefix, n->n.pxlen, e->attrs->from, e->attrs->proto->name);
	  return 0;
255 256 257 258 259
	}
    }
  return 1;
}

260
void
261
rte_free(rte *e)
262 263 264 265 266 267 268 269
{
  if (e->attrs->aflags & RTAF_CACHED)
    rta_free(e->attrs);
  sl_free(rte_slab, e);
}

static inline void
rte_free_quick(rte *e)
270 271 272 273 274
{
  rta_free(e->attrs);
  sl_free(rte_slab, e);
}

275 276 277 278 279 280 281 282 283 284 285
static int
rte_same(rte *x, rte *y)
{
  return
    x->attrs == y->attrs &&
    x->flags == y->flags &&
    x->pflags == y->pflags &&
    x->pref == y->pref &&
    (!x->attrs->proto->rte_same || x->attrs->proto->rte_same(x, y));
}

286
static void
287
rte_recalculate(rtable *table, net *net, struct proto *p, rte *new, ea_list *tmpa)
288 289 290 291 292 293 294 295 296 297
{
  rte *old_best = net->routes;
  rte *old = NULL;
  rte **k, *r, *s;

  k = &net->routes;			/* Find and remove original route from the same protocol */
  while (old = *k)
    {
      if (old->attrs->proto == p)
	{
298 299 300 301 302 303 304 305
	  if (rte_same(old, new))
	    {
	      /* No changes, ignore the new route */
	      rte_trace_in(D_ROUTES, p, new, "ignored");
	      rte_free_quick(new);
	      old->lastmod = now;
	      return;
	    }
306 307 308 309 310 311
	  *k = old->next;
	  break;
	}
      k = &old->next;
    }

312
  if (new && rte_better(new, old_best))	/* It's a new optimal route => announce and relink it */
313
    {
314
      rte_trace_in(D_ROUTES, p, new, "added [best]");
315
      rte_announce(table, net, new, old_best, tmpa);
316 317 318 319 320 321 322 323 324 325 326
      new->next = net->routes;
      net->routes = new;
    }
  else
    {
      if (old == old_best)		/* It has _replaced_ the old optimal route */
	{
	  r = new;			/* Find new optimal route and announce it */
	  for(s=net->routes; s; s=s->next)
	    if (rte_better(s, r))
	      r = s;
327
	  rte_announce(table, net, r, old_best, tmpa);
328 329 330 331 332 333 334 335 336 337
	  if (r)			/* Re-link the new optimal route */
	    {
	      k = &net->routes;
	      while (s = *k)
		{
		  if (s == r)
		    {
		      *k = r->next;
		      break;
		    }
Martin Mareš's avatar
Martin Mareš committed
338
		  k = &s->next;
339 340 341
		}
	      r->next = net->routes;
	      net->routes = r;
342 343 344 345
	      if (!r &&
		  table->gc_counter++ >= table->config->gc_max_ops &&
		  table->gc_time + table->config->gc_min_time <= now)
		ev_schedule(table->gc_event);
346 347 348 349 350 351
	    }
	}
      if (new)				/* Link in the new non-optimal route */
	{
	  new->next = old_best->next;
	  old_best->next = new;
352 353 354 355 356 357 358 359 360 361
	  rte_trace_in(D_ROUTES, p, new, "added");
	}
      else if (old && (p->debug & D_ROUTES))
	{
	  if (old != old_best)
	    rte_trace_in(D_ROUTES, p, old, "removed");
	  else if (net->routes)
	    rte_trace_in(D_ROUTES, p, old, "removed [replaced]");
	  else
	    rte_trace_in(D_ROUTES, p, old, "removed [sole]");
362 363 364
	}
    }
  if (old)
365 366 367
    {
      if (p->rte_remove)
	p->rte_remove(net, old);
368
      rte_free_quick(old);
369
    }
370 371 372 373 374 375
  if (new)
    {
      new->lastmod = now;
      if (p->rte_insert)
	p->rte_insert(net, new);
    }
376 377
}

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
static int rte_update_nest_cnt;		/* Nesting counter to allow recursive updates */

static inline void
rte_update_lock(void)
{
  rte_update_nest_cnt++;
}

static inline void
rte_update_unlock(void)
{
  if (!--rte_update_nest_cnt)
    lp_flush(rte_update_pool);
}

void
394
rte_update(rtable *table, net *net, struct proto *p, rte *new)
395 396 397 398 399 400
{
  ea_list *tmpa = NULL;

  rte_update_lock();
  if (new)
    {
401 402 403 404 405 406 407 408 409 410
      if (!rte_validate(new))
	{
	  rte_trace_in(D_FILTERS, p, new, "invalid");
	  goto drop;
	}
      if (p->in_filter == FILTER_REJECT)
	{
	  rte_trace_in(D_FILTERS, p, new, "filtered out");
	  goto drop;
	}
411 412 413 414
      if (p->make_tmp_attrs)
	tmpa = p->make_tmp_attrs(new, rte_update_pool);
      if (p->in_filter)
	{
415
	  ea_list *old_tmpa = tmpa;
416
	  int fr = f_run(p->in_filter, &new, &tmpa, rte_update_pool, 0);
417
	  if (fr > F_ACCEPT)
418 419 420 421
	    {
	      rte_trace_in(D_FILTERS, p, new, "filtered out");
	      goto drop;
	    }
422
	  if (tmpa != old_tmpa && p->store_tmp_attrs)
423 424 425 426 427 428
	    p->store_tmp_attrs(new, tmpa);
	}
      if (!(new->attrs->aflags & RTAF_CACHED)) /* Need to copy attributes */
	new->attrs = rta_lookup(new->attrs);
      new->flags |= REF_COW;
    }
429
  rte_recalculate(table, net, p, new, tmpa);
430 431 432 433 434 435 436 437
  rte_update_unlock();
  return;

drop:
  rte_free(new);
  rte_update_unlock();
}

438
void
439
rte_discard(rtable *t, rte *old)	/* Non-filtered route deletion, used during garbage collection */
440
{
441 442 443
  net *n = old->net;
  struct proto *p = old->attrs->proto;

444
  rte_update_lock();
445
  rte_recalculate(t, n, p, NULL, NULL);
446
  rte_update_unlock();
447 448 449
}

void
450
rte_dump(rte *e)
451
{
452
  net *n = e->net;
453
  if (n)
454
    debug("%1I/%2d ", n->n.prefix, n->n.pxlen);
455 456
  else
    debug("??? ");
457
  debug("KF=%02x PF=%02x pref=%d lm=%d ", n->n.flags, e->pflags, e->pref, now-e->lastmod);
458
  rta_dump(e->attrs);
459 460
  if (e->attrs->proto->proto->dump_attrs)
    e->attrs->proto->proto->dump_attrs(e);
461
  debug("\n");
462
}
463

464 465 466
void
rt_dump(rtable *t)
{
467 468
  rte *e;
  net *n;
469
  struct announce_hook *a;
470 471

  debug("Dump of routing table <%s>\n", t->name);
472
#ifdef DEBUGGING
473
  fib_check(&t->fib);
474
#endif
475 476 477 478 479
  FIB_WALK(&t->fib, fn)
    {
      n = (net *) fn;
      for(e=n->routes; e; e=e->next)
	rte_dump(e);
480
    }
481
  FIB_WALK_END;
482 483
  WALK_LIST(a, t->hooks)
    debug("\tAnnounces routes to protocol %s\n", a->proto->name);
484
  debug("\n");
485
}
486

487 488 489
void
rt_dump_all(void)
{
490 491 492 493
  rtable *t;

  WALK_LIST(t, routing_tables)
    rt_dump(t);
494 495
}

496
static void
497
rt_gc(void *tab)
498
{
499
  rtable *t = tab;
500

501 502 503
  DBG("Entered routing table garbage collector for %s after %d seconds and %d deletes\n",
      t->name, (int)(now - t->gc_time), t->gc_counter);
  rt_prune(t);
504 505
}

506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
void
rt_setup(pool *p, rtable *t, char *name, struct rtable_config *cf)
{
  bzero(t, sizeof(*t));
  fib_init(&t->fib, p, sizeof(net), 0, rte_init);
  t->name = name;
  t->config = cf;
  init_list(&t->hooks);
  if (cf)
    {
      t->gc_event = ev_new(p);
      t->gc_event->hook = rt_gc;
      t->gc_event->data = t;
    }
}

522 523 524 525
void
rt_init(void)
{
  rta_init();
526
  rt_table_pool = rp_new(&root_pool, "Routing tables");
527
  rte_update_pool = lp_new(rt_table_pool, 4080);
528
  rte_slab = sl_new(rt_table_pool, sizeof(rte));
529
  init_list(&routing_tables);
530
}
531 532 533 534 535

void
rt_prune(rtable *tab)
{
  struct fib_iterator fit;
536
  int rcnt = 0, rdel = 0, ncnt = 0, ndel = 0;
537 538

  DBG("Pruning route table %s\n", tab->name);
539 540 541
  FIB_ITERATE_INIT(&fit, &tab->fib);
again:
  FIB_ITERATE_START(&tab->fib, &fit, f)
542
    {
543 544 545 546 547 548 549
      net *n = (net *) f;
      rte *e;
      ncnt++;
    rescan:
      for (e=n->routes; e; e=e->next, rcnt++)
	if (e->attrs->proto->core_state != FS_HAPPY)
	  {
550
	    rte_discard(tab, e);
551 552 553 554
	    rdel++;
	    goto rescan;
	  }
      if (!n->routes)		/* Orphaned FIB entry? */
555
	{
556 557 558 559
	  FIB_ITERATE_PUT(&fit, f);
	  fib_delete(&tab->fib, f);
	  ndel++;
	  goto again;
560 561
	}
    }
562
  FIB_ITERATE_END(f);
563
  DBG("Pruned %d of %d routes and %d of %d networks\n", rcnt, rdel, ncnt, ndel);
564 565
  tab->gc_counter = 0;
  tab->gc_time = now;
566
}
567 568 569 570 571 572 573 574 575 576

void
rt_prune_all(void)
{
  rtable *t;

  WALK_LIST(t, routing_tables)
    rt_prune(t);
}

577 578 579 580 581 582 583 584 585 586 587 588 589
struct rtable_config *
rt_new_table(struct symbol *s)
{
  struct rtable_config *c = cfg_allocz(sizeof(struct rtable_config));

  cf_define_symbol(s, SYM_TABLE, c);
  c->name = s->name;
  add_tail(&new_config->tables, &c->n);
  c->gc_max_ops = 100;
  c->gc_min_time = 5;
  return c;
}

590 591 592 593 594 595
void
rt_preconfig(struct config *c)
{
  struct symbol *s = cf_find_symbol("master");

  init_list(&c->tables);
596
  c->master_rtc = rt_new_table(s);
597 598 599
}

void
600
rt_lock_table(rtable *r)
601
{
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
  r->use_count++;
}

void
rt_unlock_table(rtable *r)
{
  if (!--r->use_count && r->deleted)
    {
      struct config *conf = r->deleted;
      DBG("Deleting routing table %s\n", r->name);
      rem_node(&r->n);
      fib_free(&r->fib);
      mb_free(r);
      config_del_obstacle(conf);
    }
}

void
rt_commit(struct config *new, struct config *old)
{
  struct rtable_config *o, *r;
623

624 625
  DBG("rt_commit:\n");
  if (old)
626
    {
627 628 629 630 631 632
      WALK_LIST(o, old->tables)
	{
	  rtable *ot = o->table;
	  if (!ot->deleted)
	    {
	      struct symbol *sym = cf_find_symbol(o->name);
633
	      if (sym && sym->class == SYM_TABLE && !new->shutdown)
634 635 636 637 638
		{
		  DBG("\t%s: same\n", o->name);
		  r = sym->def;
		  r->table = ot;
		  ot->name = r->name;
639
		  ot->config = r;
640 641 642
		}
	      else
		{
643
		  DBG("\t%s: deleted\n", o->name);
644 645 646 647 648 649 650
		  ot->deleted = old;
		  config_add_obstacle(old);
		  rt_lock_table(ot);
		  rt_unlock_table(ot);
		}
	    }
	}
651
    }
652 653 654 655 656 657

  WALK_LIST(r, new->tables)
    if (!r->table)
      {
	rtable *t = mb_alloc(rt_table_pool, sizeof(struct rtable));
	DBG("\t%s: created\n", r->name);
658
	rt_setup(rt_table_pool, t, r->name, r);
659 660 661 662
	add_tail(&routing_tables, &t->n);
	r->table = t;
      }
  DBG("\tdone\n");
663
}
664 665 666 667 668 669

/*
 *  CLI commands
 */

static void
670
rt_format_via(rte *e, byte *via)
671 672 673 674 675 676 677 678 679 680 681 682
{
  rta *a = e->attrs;

  switch (a->dest)
    {
    case RTD_ROUTER:	bsprintf(via, "via %I on %s", a->gw, a->iface->name); break;
    case RTD_DEVICE:	bsprintf(via, "dev %s", a->iface->name); break;
    case RTD_BLACKHOLE:	bsprintf(via, "blackhole"); break;
    case RTD_UNREACHABLE:	bsprintf(via, "unreachable"); break;
    case RTD_PROHIBIT:	bsprintf(via, "prohibited"); break;
    default:		bsprintf(via, "???");
    }
683 684 685 686 687
}

static void
rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d)
{
688
  byte via[STD_ADDRESS_P_LENGTH+32], from[STD_ADDRESS_P_LENGTH+6];
689 690 691 692
  byte tm[TM_RELTIME_BUFFER_SIZE], info[256];
  rta *a = e->attrs;

  rt_format_via(e, via);
693 694 695 696 697 698 699 700 701 702 703
  tm_format_reltime(tm, e->lastmod);
  if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->gw))
    bsprintf(from, " from %I", a->from);
  else
    from[0] = 0;
  if (a->proto->proto->get_route_info)
    a->proto->proto->get_route_info(e, info);
  else
    bsprintf(info, " (%d)", e->pref);
  cli_printf(c, -1007, "%-18s %s [%s %s%s]%s", ia, via, a->proto->name, tm, from, info);
  if (d->verbose)
704
    rta_show(c, a);
705 706 707 708 709 710 711 712 713 714 715 716 717 718
}

static void
rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
{
  rte *e, *ee;
  byte ia[STD_ADDRESS_P_LENGTH+8];

  bsprintf(ia, "%I/%d", n->n.prefix, n->n.pxlen);
  for(e=n->routes; e; e=e->next)
    {
      struct ea_list *tmpa = NULL;
      ee = e;
      rte_update_lock();		/* We use the update buffer for filtering */
719
      if (d->filter == FILTER_ACCEPT || f_run(d->filter, &ee, &tmpa, rte_update_pool, 0) <= F_ACCEPT)
720 721 722 723 724 725 726 727 728 729 730 731 732 733
	{
	  rt_show_rte(c, ia, e, d);
	  ia[0] = 0;
	}
      if (e != ee)
	rte_free(ee);
      rte_update_unlock();
    }
}

static void
rt_show_cont(struct cli *c)
{
  struct rt_show_data *d = c->rover;
734 735 736 737 738
#ifdef DEBUGGING
  unsigned max = 4;
#else
  unsigned max = 64;
#endif
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
  struct fib *fib = &d->table->fib;
  struct fib_iterator *it = &d->fit;

  FIB_ITERATE_START(fib, it, f)
    {
      net *n = (net *) f;
      if (!max--)
	{
	  FIB_ITERATE_PUT(it, f);
	  return;
	}
      rt_show_net(c, n, d);
    }
  FIB_ITERATE_END(f);
  cli_printf(c, 0, "");
  c->cont = c->cleanup = NULL;
}

static void
rt_show_cleanup(struct cli *c)
{
  struct rt_show_data *d = c->rover;

  /* Unlink the iterator */
  fit_get(&d->table->fib, &d->fit);
}

void
rt_show(struct rt_show_data *d)
{
  net *n;

  if (d->pxlen == 256)
    {
      FIB_ITERATE_INIT(&d->fit, &d->table->fib);
      this_cli->cont = rt_show_cont;
      this_cli->cleanup = rt_show_cleanup;
      this_cli->rover = d;
    }
  else
    {
      n = fib_find(&d->table->fib, &d->prefix, d->pxlen);
      if (n)
	{
	  rt_show_net(this_cli, n, d);
	  cli_msg(0, "");
	}
      else
	cli_msg(8001, "Network not in table");
    }
}