Annotation of embedaddon/bird/proto/rip/rip.c, revision 1.1
1.1 ! misho 1: /*
! 2: * BIRD -- Routing Information Protocol (RIP)
! 3: *
! 4: * (c) 1998--1999 Pavel Machek <pavel@ucw.cz>
! 5: * (c) 2004--2013 Ondrej Filip <feela@network.cz>
! 6: * (c) 2009--2015 Ondrej Zajicek <santiago@crfreenet.org>
! 7: * (c) 2009--2015 CZ.NIC z.s.p.o.
! 8: *
! 9: * Can be freely distributed and used under the terms of the GNU GPL.
! 10: */
! 11:
! 12: /**
! 13: * DOC: Routing Information Protocol (RIP)
! 14: *
! 15: * The RIP protocol is implemented in two files: |rip.c| containing the protocol
! 16: * logic, route management and the protocol glue with BIRD core, and |packets.c|
! 17: * handling RIP packet processing, RX, TX and protocol sockets.
! 18: *
! 19: * Each instance of RIP is described by a structure &rip_proto, which contains
! 20: * an internal RIP routing table, a list of protocol interfaces and the main
! 21: * timer responsible for RIP routing table cleanup.
! 22: *
! 23: * RIP internal routing table contains incoming and outgoing routes. For each
! 24: * network (represented by structure &rip_entry) there is one outgoing route
! 25: * stored directly in &rip_entry and an one-way linked list of incoming routes
! 26: * (structures &rip_rte). The list contains incoming routes from different RIP
! 27: * neighbors, but only routes with the lowest metric are stored (i.e., all
! 28: * stored incoming routes have the same metric).
! 29: *
! 30: * Note that RIP itself does not select outgoing route, that is done by the core
! 31: * routing table. When a new incoming route is received, it is propagated to the
! 32: * RIP table by rip_update_rte() and possibly stored in the list of incoming
! 33: * routes. Then the change may be propagated to the core by rip_announce_rte().
! 34: * The core selects the best route and propagate it to RIP by rip_rt_notify(),
! 35: * which updates outgoing route part of &rip_entry and possibly triggers route
! 36: * propagation by rip_trigger_update().
! 37: *
! 38: * RIP interfaces are represented by structures &rip_iface. A RIP interface
! 39: * contains a per-interface socket, a list of associated neighbors, interface
! 40: * configuration, and state information related to scheduled interface events
! 41: * and running update sessions. RIP interfaces are added and removed based on
! 42: * core interface notifications.
! 43: *
! 44: * There are two RIP interface events - regular updates and triggered updates.
! 45: * Both are managed from the RIP interface timer (rip_iface_timer()). Regular
! 46: * updates are called at fixed interval and propagate the whole routing table,
! 47: * while triggered updates are scheduled by rip_trigger_update() due to some
! 48: * routing table change and propagate only the routes modified since the time
! 49: * they were scheduled. There are also unicast-destined requested updates, but
! 50: * these are sent directly as a reaction to received RIP request message. The
! 51: * update session is started by rip_send_table(). There may be at most one
! 52: * active update session per interface, as the associated state (including the
! 53: * fib iterator) is stored directly in &rip_iface structure.
! 54: *
! 55: * RIP neighbors are represented by structures &rip_neighbor. Compared to
! 56: * neighbor handling in other routing protocols, RIP does not have explicit
! 57: * neighbor discovery and adjacency maintenance, which makes the &rip_neighbor
! 58: * related code a bit peculiar. RIP neighbors are interlinked with core neighbor
! 59: * structures (&neighbor) and use core neighbor notifications to ensure that RIP
! 60: * neighbors are timely removed. RIP neighbors are added based on received route
! 61: * notifications and removed based on core neighbor and RIP interface events.
! 62: *
! 63: * RIP neighbors are linked by RIP routes and use counter to track the number of
! 64: * associated routes, but when these RIP routes timeout, associated RIP neighbor
! 65: * is still alive (with zero counter). When RIP neighbor is removed but still
! 66: * has some associated routes, it is not freed, just changed to detached state
! 67: * (core neighbors and RIP ifaces are unlinked), then during the main timer
! 68: * cleanup phase the associated routes are removed and the &rip_neighbor
! 69: * structure is finally freed.
! 70: *
! 71: * Supported standards:
! 72: * - RFC 1058 - RIPv1
! 73: * - RFC 2453 - RIPv2
! 74: * - RFC 2080 - RIPng
! 75: * - RFC 4822 - RIP cryptographic authentication
! 76: */
! 77:
! 78: #include <stdlib.h>
! 79: #include "rip.h"
! 80:
! 81:
! 82: static inline void rip_lock_neighbor(struct rip_neighbor *n);
! 83: static inline void rip_unlock_neighbor(struct rip_neighbor *n);
! 84: static inline int rip_iface_link_up(struct rip_iface *ifa);
! 85: static inline void rip_kick_timer(struct rip_proto *p);
! 86: static inline void rip_iface_kick_timer(struct rip_iface *ifa);
! 87: static void rip_iface_timer(timer *timer);
! 88: static void rip_trigger_update(struct rip_proto *p);
! 89:
! 90:
! 91: /*
! 92: * RIP routes
! 93: */
! 94:
! 95: static void
! 96: rip_init_entry(struct fib_node *fn)
! 97: {
! 98: // struct rip_entry *en = (void) *fn;
! 99:
! 100: const uint offset = OFFSETOF(struct rip_entry, routes);
! 101: memset((byte *)fn + offset, 0, sizeof(struct rip_entry) - offset);
! 102: }
! 103:
! 104: static struct rip_rte *
! 105: rip_add_rte(struct rip_proto *p, struct rip_rte **rp, struct rip_rte *src)
! 106: {
! 107: struct rip_rte *rt = sl_alloc(p->rte_slab);
! 108:
! 109: memcpy(rt, src, sizeof(struct rip_rte));
! 110: rt->next = *rp;
! 111: *rp = rt;
! 112:
! 113: rip_lock_neighbor(rt->from);
! 114:
! 115: return rt;
! 116: }
! 117:
! 118: static inline void
! 119: rip_remove_rte(struct rip_proto *p, struct rip_rte **rp)
! 120: {
! 121: struct rip_rte *rt = *rp;
! 122:
! 123: rip_unlock_neighbor(rt->from);
! 124:
! 125: *rp = rt->next;
! 126: sl_free(p->rte_slab, rt);
! 127: }
! 128:
! 129: static inline int rip_same_rte(struct rip_rte *a, struct rip_rte *b)
! 130: { return a->metric == b->metric && a->tag == b->tag && ipa_equal(a->next_hop, b->next_hop); }
! 131:
! 132: static inline int rip_valid_rte(struct rip_rte *rt)
! 133: { return rt->from->ifa != NULL; }
! 134:
! 135: /**
! 136: * rip_announce_rte - announce route from RIP routing table to the core
! 137: * @p: RIP instance
! 138: * @en: related network
! 139: *
! 140: * The function takes a list of incoming routes from @en, prepare appropriate
! 141: * &rte for the core and propagate it by rte_update().
! 142: */
! 143: static void
! 144: rip_announce_rte(struct rip_proto *p, struct rip_entry *en)
! 145: {
! 146: struct rip_rte *rt = en->routes;
! 147:
! 148: /* Find first valid rte */
! 149: while (rt && !rip_valid_rte(rt))
! 150: rt = rt->next;
! 151:
! 152: if (rt)
! 153: {
! 154: /* Update */
! 155: net *n = net_get(p->p.table, en->n.prefix, en->n.pxlen);
! 156:
! 157: rta a0 = {
! 158: .src = p->p.main_source,
! 159: .source = RTS_RIP,
! 160: .scope = SCOPE_UNIVERSE,
! 161: .cast = RTC_UNICAST
! 162: };
! 163:
! 164: u8 rt_metric = rt->metric;
! 165: u16 rt_tag = rt->tag;
! 166: struct rip_rte *rt2 = rt->next;
! 167:
! 168: /* Find second valid rte */
! 169: while (rt2 && !rip_valid_rte(rt2))
! 170: rt2 = rt2->next;
! 171:
! 172: if (p->ecmp && rt2)
! 173: {
! 174: /* ECMP route */
! 175: struct mpnh *nhs = NULL;
! 176: int num = 0;
! 177:
! 178: for (rt = en->routes; rt && (num < p->ecmp); rt = rt->next)
! 179: {
! 180: if (!rip_valid_rte(rt))
! 181: continue;
! 182:
! 183: struct mpnh *nh = alloca(sizeof(struct mpnh));
! 184: nh->gw = rt->next_hop;
! 185: nh->iface = rt->from->nbr->iface;
! 186: nh->weight = rt->from->ifa->cf->ecmp_weight;
! 187: mpnh_insert(&nhs, nh);
! 188: num++;
! 189:
! 190: if (rt->tag != rt_tag)
! 191: rt_tag = 0;
! 192: }
! 193:
! 194: a0.dest = RTD_MULTIPATH;
! 195: a0.nexthops = nhs;
! 196: }
! 197: else
! 198: {
! 199: /* Unipath route */
! 200: a0.dest = RTD_ROUTER;
! 201: a0.gw = rt->next_hop;
! 202: a0.iface = rt->from->nbr->iface;
! 203: a0.from = rt->from->nbr->addr;
! 204: }
! 205:
! 206: rta *a = rta_lookup(&a0);
! 207: rte *e = rte_get_temp(a);
! 208:
! 209: e->u.rip.from = a0.iface;
! 210: e->u.rip.metric = rt_metric;
! 211: e->u.rip.tag = rt_tag;
! 212:
! 213: e->net = n;
! 214: e->pflags = 0;
! 215:
! 216: rte_update(&p->p, n, e);
! 217: }
! 218: else
! 219: {
! 220: /* Withdraw */
! 221: net *n = net_find(p->p.table, en->n.prefix, en->n.pxlen);
! 222: rte_update(&p->p, n, NULL);
! 223: }
! 224: }
! 225:
! 226: /**
! 227: * rip_update_rte - enter a route update to RIP routing table
! 228: * @p: RIP instance
! 229: * @prefix: network prefix
! 230: * @pxlen: network prefix length
! 231: * @new: a &rip_rte representing the new route
! 232: *
! 233: * The function is called by the RIP packet processing code whenever it receives
! 234: * a reachable route. The appropriate routing table entry is found and the list
! 235: * of incoming routes is updated. Eventually, the change is also propagated to
! 236: * the core by rip_announce_rte(). Note that for unreachable routes,
! 237: * rip_withdraw_rte() should be called instead of rip_update_rte().
! 238: */
! 239: void
! 240: rip_update_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_rte *new)
! 241: {
! 242: struct rip_entry *en = fib_get(&p->rtable, prefix, pxlen);
! 243: struct rip_rte *rt, **rp;
! 244: int changed = 0;
! 245:
! 246: /* If the new route is better, remove all current routes */
! 247: if (en->routes && new->metric < en->routes->metric)
! 248: while (en->routes)
! 249: rip_remove_rte(p, &en->routes);
! 250:
! 251: /* Find the old route (also set rp for later) */
! 252: for (rp = &en->routes; rt = *rp; rp = &rt->next)
! 253: if (rt->from == new->from)
! 254: {
! 255: if (rip_same_rte(rt, new))
! 256: {
! 257: rt->expires = new->expires;
! 258: return;
! 259: }
! 260:
! 261: /* Remove the old route */
! 262: rip_remove_rte(p, rp);
! 263: changed = 1;
! 264: break;
! 265: }
! 266:
! 267: /* If the new route is optimal, add it to the list */
! 268: if (!en->routes || new->metric == en->routes->metric)
! 269: {
! 270: rt = rip_add_rte(p, rp, new);
! 271: changed = 1;
! 272: }
! 273:
! 274: /* Announce change if on relevant position (the first or any for ECMP) */
! 275: if (changed && (rp == &en->routes || p->ecmp))
! 276: rip_announce_rte(p, en);
! 277: }
! 278:
! 279: /**
! 280: * rip_withdraw_rte - enter a route withdraw to RIP routing table
! 281: * @p: RIP instance
! 282: * @prefix: network prefix
! 283: * @pxlen: network prefix length
! 284: * @from: a &rip_neighbor propagating the withdraw
! 285: *
! 286: * The function is called by the RIP packet processing code whenever it receives
! 287: * an unreachable route. The incoming route for given network from nbr @from is
! 288: * removed. Eventually, the change is also propagated by rip_announce_rte().
! 289: */
! 290: void
! 291: rip_withdraw_rte(struct rip_proto *p, ip_addr *prefix, int pxlen, struct rip_neighbor *from)
! 292: {
! 293: struct rip_entry *en = fib_find(&p->rtable, prefix, pxlen);
! 294: struct rip_rte *rt, **rp;
! 295:
! 296: if (!en)
! 297: return;
! 298:
! 299: /* Find the old route */
! 300: for (rp = &en->routes; rt = *rp; rp = &rt->next)
! 301: if (rt->from == from)
! 302: break;
! 303:
! 304: if (!rt)
! 305: return;
! 306:
! 307: /* Remove the old route */
! 308: rip_remove_rte(p, rp);
! 309:
! 310: /* Announce change if on relevant position */
! 311: if (rp == &en->routes || p->ecmp)
! 312: rip_announce_rte(p, en);
! 313: }
! 314:
! 315: /*
! 316: * rip_rt_notify - core tells us about new route, so store
! 317: * it into our data structures.
! 318: */
! 319: static void
! 320: rip_rt_notify(struct proto *P, struct rtable *table UNUSED, struct network *net, struct rte *new,
! 321: struct rte *old UNUSED, struct ea_list *attrs)
! 322: {
! 323: struct rip_proto *p = (struct rip_proto *) P;
! 324: struct rip_entry *en;
! 325: int old_metric;
! 326:
! 327: if (new)
! 328: {
! 329: /* Update */
! 330: u32 rt_metric = ea_get_int(attrs, EA_RIP_METRIC, 1);
! 331: u32 rt_tag = ea_get_int(attrs, EA_RIP_TAG, 0);
! 332:
! 333: if (rt_metric > p->infinity)
! 334: {
! 335: log(L_WARN "%s: Invalid rip_metric value %u for route %I/%d",
! 336: p->p.name, rt_metric, net->n.prefix, net->n.pxlen);
! 337: rt_metric = p->infinity;
! 338: }
! 339:
! 340: if (rt_tag > 0xffff)
! 341: {
! 342: log(L_WARN "%s: Invalid rip_tag value %u for route %I/%d",
! 343: p->p.name, rt_tag, net->n.prefix, net->n.pxlen);
! 344: rt_metric = p->infinity;
! 345: rt_tag = 0;
! 346: }
! 347:
! 348: /*
! 349: * Note that we accept exported routes with infinity metric (this could
! 350: * happen if rip_metric is modified in filters). Such entry has infinity
! 351: * metric but is RIP_ENTRY_VALID and therefore is not subject to garbage
! 352: * collection.
! 353: */
! 354:
! 355: en = fib_get(&p->rtable, &net->n.prefix, net->n.pxlen);
! 356:
! 357: old_metric = en->valid ? en->metric : -1;
! 358:
! 359: en->valid = RIP_ENTRY_VALID;
! 360: en->metric = rt_metric;
! 361: en->tag = rt_tag;
! 362: en->from = (new->attrs->src->proto == P) ? new->u.rip.from : NULL;
! 363: en->iface = new->attrs->iface;
! 364: en->next_hop = new->attrs->gw;
! 365: }
! 366: else
! 367: {
! 368: /* Withdraw */
! 369: en = fib_find(&p->rtable, &net->n.prefix, net->n.pxlen);
! 370:
! 371: if (!en || en->valid != RIP_ENTRY_VALID)
! 372: return;
! 373:
! 374: old_metric = en->metric;
! 375:
! 376: en->valid = RIP_ENTRY_STALE;
! 377: en->metric = p->infinity;
! 378: en->tag = 0;
! 379: en->from = NULL;
! 380: en->iface = NULL;
! 381: en->next_hop = IPA_NONE;
! 382: }
! 383:
! 384: /* Activate triggered updates */
! 385: if (en->metric != old_metric)
! 386: {
! 387: en->changed = now;
! 388: rip_trigger_update(p);
! 389: }
! 390: }
! 391:
! 392:
! 393: /*
! 394: * RIP neighbors
! 395: */
! 396:
! 397: struct rip_neighbor *
! 398: rip_get_neighbor(struct rip_proto *p, ip_addr *a, struct rip_iface *ifa)
! 399: {
! 400: neighbor *nbr = neigh_find2(&p->p, a, ifa->iface, 0);
! 401:
! 402: if (!nbr || (nbr->scope == SCOPE_HOST) || !rip_iface_link_up(ifa))
! 403: return NULL;
! 404:
! 405: if (nbr->data)
! 406: return nbr->data;
! 407:
! 408: TRACE(D_EVENTS, "New neighbor %I on %s", *a, ifa->iface->name);
! 409:
! 410: struct rip_neighbor *n = mb_allocz(p->p.pool, sizeof(struct rip_neighbor));
! 411: n->ifa = ifa;
! 412: n->nbr = nbr;
! 413: nbr->data = n;
! 414: n->csn = nbr->aux;
! 415:
! 416: add_tail(&ifa->neigh_list, NODE n);
! 417:
! 418: return n;
! 419: }
! 420:
! 421: static void
! 422: rip_remove_neighbor(struct rip_proto *p, struct rip_neighbor *n)
! 423: {
! 424: neighbor *nbr = n->nbr;
! 425:
! 426: TRACE(D_EVENTS, "Removing neighbor %I on %s", nbr->addr, nbr->iface->name);
! 427:
! 428: rem_node(NODE n);
! 429: n->ifa = NULL;
! 430: n->nbr = NULL;
! 431: nbr->data = NULL;
! 432: nbr->aux = n->csn;
! 433:
! 434: rfree(n->bfd_req);
! 435: n->bfd_req = NULL;
! 436: n->last_seen = 0;
! 437:
! 438: if (!n->uc)
! 439: mb_free(n);
! 440:
! 441: /* Related routes are removed in rip_timer() */
! 442: rip_kick_timer(p);
! 443: }
! 444:
! 445: static inline void
! 446: rip_lock_neighbor(struct rip_neighbor *n)
! 447: {
! 448: n->uc++;
! 449: }
! 450:
! 451: static inline void
! 452: rip_unlock_neighbor(struct rip_neighbor *n)
! 453: {
! 454: n->uc--;
! 455:
! 456: if (!n->nbr && !n->uc)
! 457: mb_free(n);
! 458: }
! 459:
! 460: static void
! 461: rip_neigh_notify(struct neighbor *nbr)
! 462: {
! 463: struct rip_proto *p = (struct rip_proto *) nbr->proto;
! 464: struct rip_neighbor *n = nbr->data;
! 465:
! 466: if (!n)
! 467: return;
! 468:
! 469: /*
! 470: * We assume that rip_neigh_notify() is called before rip_if_notify() for
! 471: * IF_CHANGE_DOWN and therefore n->ifa is still valid. We have no such
! 472: * ordering assumption for IF_CHANGE_LINK, so we test link state of the
! 473: * underlying iface instead of just rip_iface state.
! 474: */
! 475: if ((nbr->scope <= 0) || !rip_iface_link_up(n->ifa))
! 476: rip_remove_neighbor(p, n);
! 477: }
! 478:
! 479: static void
! 480: rip_bfd_notify(struct bfd_request *req)
! 481: {
! 482: struct rip_neighbor *n = req->data;
! 483: struct rip_proto *p = n->ifa->rip;
! 484:
! 485: if (req->down)
! 486: {
! 487: TRACE(D_EVENTS, "BFD session down for nbr %I on %s",
! 488: n->nbr->addr, n->ifa->iface->name);
! 489: rip_remove_neighbor(p, n);
! 490: }
! 491: }
! 492:
! 493: void
! 494: rip_update_bfd(struct rip_proto *p, struct rip_neighbor *n)
! 495: {
! 496: int use_bfd = n->ifa->cf->bfd && n->last_seen;
! 497:
! 498: if (use_bfd && !n->bfd_req)
! 499: {
! 500: /*
! 501: * For RIPv2, use the same address as rip_open_socket(). For RIPng, neighbor
! 502: * should contain an address from the same prefix, thus also link-local. It
! 503: * may cause problems if two link-local addresses are assigned to one iface.
! 504: */
! 505: ip_addr saddr = rip_is_v2(p) ? n->ifa->sk->saddr : n->nbr->ifa->ip;
! 506: n->bfd_req = bfd_request_session(p->p.pool, n->nbr->addr, saddr,
! 507: n->nbr->iface, rip_bfd_notify, n);
! 508: }
! 509:
! 510: if (!use_bfd && n->bfd_req)
! 511: {
! 512: rfree(n->bfd_req);
! 513: n->bfd_req = NULL;
! 514: }
! 515: }
! 516:
! 517:
! 518: /*
! 519: * RIP interfaces
! 520: */
! 521:
! 522: static void
! 523: rip_iface_start(struct rip_iface *ifa)
! 524: {
! 525: struct rip_proto *p = ifa->rip;
! 526:
! 527: TRACE(D_EVENTS, "Starting interface %s", ifa->iface->name);
! 528:
! 529: ifa->next_regular = now + (random() % ifa->cf->update_time) + 1;
! 530: ifa->next_triggered = now; /* Available immediately */
! 531: ifa->want_triggered = 1; /* All routes in triggered update */
! 532: tm_start(ifa->timer, 1); /* Or 100 ms */
! 533: ifa->up = 1;
! 534:
! 535: if (!ifa->cf->passive)
! 536: rip_send_request(ifa->rip, ifa);
! 537: }
! 538:
! 539: static void
! 540: rip_iface_stop(struct rip_iface *ifa)
! 541: {
! 542: struct rip_proto *p = ifa->rip;
! 543: struct rip_neighbor *n;
! 544:
! 545: TRACE(D_EVENTS, "Stopping interface %s", ifa->iface->name);
! 546:
! 547: rip_reset_tx_session(p, ifa);
! 548:
! 549: WALK_LIST_FIRST(n, ifa->neigh_list)
! 550: rip_remove_neighbor(p, n);
! 551:
! 552: tm_stop(ifa->timer);
! 553: ifa->up = 0;
! 554: }
! 555:
! 556: static inline int
! 557: rip_iface_link_up(struct rip_iface *ifa)
! 558: {
! 559: return !ifa->cf->check_link || (ifa->iface->flags & IF_LINK_UP);
! 560: }
! 561:
! 562: static void
! 563: rip_iface_update_state(struct rip_iface *ifa)
! 564: {
! 565: int up = ifa->sk && rip_iface_link_up(ifa);
! 566:
! 567: if (up == ifa->up)
! 568: return;
! 569:
! 570: if (up)
! 571: rip_iface_start(ifa);
! 572: else
! 573: rip_iface_stop(ifa);
! 574: }
! 575:
! 576: static void
! 577: rip_iface_update_buffers(struct rip_iface *ifa)
! 578: {
! 579: if (!ifa->sk)
! 580: return;
! 581:
! 582: uint rbsize = ifa->cf->rx_buffer ?: ifa->iface->mtu;
! 583: uint tbsize = ifa->cf->tx_length ?: ifa->iface->mtu;
! 584: rbsize = MAX(rbsize, tbsize);
! 585:
! 586: sk_set_rbsize(ifa->sk, rbsize);
! 587: sk_set_tbsize(ifa->sk, tbsize);
! 588:
! 589: uint headers = (rip_is_v2(ifa->rip) ? IP4_HEADER_LENGTH : IP6_HEADER_LENGTH) + UDP_HEADER_LENGTH;
! 590: ifa->tx_plen = tbsize - headers;
! 591:
! 592: if (ifa->cf->auth_type == RIP_AUTH_CRYPTO)
! 593: ifa->tx_plen -= RIP_AUTH_TAIL_LENGTH + max_mac_length(ifa->cf->passwords);
! 594: }
! 595:
! 596: static inline void
! 597: rip_iface_update_bfd(struct rip_iface *ifa)
! 598: {
! 599: struct rip_proto *p = ifa->rip;
! 600: struct rip_neighbor *n;
! 601:
! 602: WALK_LIST(n, ifa->neigh_list)
! 603: rip_update_bfd(p, n);
! 604: }
! 605:
! 606:
! 607: static void
! 608: rip_iface_locked(struct object_lock *lock)
! 609: {
! 610: struct rip_iface *ifa = lock->data;
! 611: struct rip_proto *p = ifa->rip;
! 612:
! 613: if (!rip_open_socket(ifa))
! 614: {
! 615: log(L_ERR "%s: Cannot open socket for %s", p->p.name, ifa->iface->name);
! 616: return;
! 617: }
! 618:
! 619: rip_iface_update_buffers(ifa);
! 620: rip_iface_update_state(ifa);
! 621: }
! 622:
! 623:
! 624: static struct rip_iface *
! 625: rip_find_iface(struct rip_proto *p, struct iface *what)
! 626: {
! 627: struct rip_iface *ifa;
! 628:
! 629: WALK_LIST(ifa, p->iface_list)
! 630: if (ifa->iface == what)
! 631: return ifa;
! 632:
! 633: return NULL;
! 634: }
! 635:
! 636: static void
! 637: rip_add_iface(struct rip_proto *p, struct iface *iface, struct rip_iface_config *ic)
! 638: {
! 639: struct rip_iface *ifa;
! 640:
! 641: TRACE(D_EVENTS, "Adding interface %s", iface->name);
! 642:
! 643: ifa = mb_allocz(p->p.pool, sizeof(struct rip_iface));
! 644: ifa->rip = p;
! 645: ifa->iface = iface;
! 646: ifa->cf = ic;
! 647:
! 648: if (ipa_nonzero(ic->address))
! 649: ifa->addr = ic->address;
! 650: else if (ic->mode == RIP_IM_MULTICAST)
! 651: ifa->addr = rip_is_v2(p) ? IP4_RIP_ROUTERS : IP6_RIP_ROUTERS;
! 652: else /* Broadcast */
! 653: ifa->addr = iface->addr->brd;
! 654:
! 655: init_list(&ifa->neigh_list);
! 656:
! 657: add_tail(&p->iface_list, NODE ifa);
! 658:
! 659: ifa->timer = tm_new_set(p->p.pool, rip_iface_timer, ifa, 0, 0);
! 660:
! 661: struct object_lock *lock = olock_new(p->p.pool);
! 662: lock->type = OBJLOCK_UDP;
! 663: lock->port = ic->port;
! 664: lock->iface = iface;
! 665: lock->data = ifa;
! 666: lock->hook = rip_iface_locked;
! 667: ifa->lock = lock;
! 668:
! 669: olock_acquire(lock);
! 670: }
! 671:
! 672: static void
! 673: rip_remove_iface(struct rip_proto *p, struct rip_iface *ifa)
! 674: {
! 675: rip_iface_stop(ifa);
! 676:
! 677: TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name);
! 678:
! 679: rem_node(NODE ifa);
! 680:
! 681: rfree(ifa->sk);
! 682: rfree(ifa->lock);
! 683: rfree(ifa->timer);
! 684:
! 685: mb_free(ifa);
! 686: }
! 687:
! 688: static int
! 689: rip_reconfigure_iface(struct rip_proto *p, struct rip_iface *ifa, struct rip_iface_config *new)
! 690: {
! 691: struct rip_iface_config *old = ifa->cf;
! 692:
! 693: /* Change of these options would require to reset the iface socket */
! 694: if ((new->mode != old->mode) ||
! 695: (new->port != old->port) ||
! 696: (new->tx_tos != old->tx_tos) ||
! 697: (new->tx_priority != old->tx_priority) ||
! 698: (new->ttl_security != old->ttl_security))
! 699: return 0;
! 700:
! 701: TRACE(D_EVENTS, "Reconfiguring interface %s", ifa->iface->name);
! 702:
! 703: ifa->cf = new;
! 704:
! 705: rip_iface_update_buffers(ifa);
! 706:
! 707: if (ifa->next_regular > (now + new->update_time))
! 708: ifa->next_regular = now + (random() % new->update_time) + 1;
! 709:
! 710: if (new->check_link != old->check_link)
! 711: rip_iface_update_state(ifa);
! 712:
! 713: if (new->bfd != old->bfd)
! 714: rip_iface_update_bfd(ifa);
! 715:
! 716: if (ifa->up)
! 717: rip_iface_kick_timer(ifa);
! 718:
! 719: return 1;
! 720: }
! 721:
! 722: static void
! 723: rip_reconfigure_ifaces(struct rip_proto *p, struct rip_config *cf)
! 724: {
! 725: struct iface *iface;
! 726:
! 727: WALK_LIST(iface, iface_list)
! 728: {
! 729: if (! (iface->flags & IF_UP))
! 730: continue;
! 731:
! 732: struct rip_iface *ifa = rip_find_iface(p, iface);
! 733: struct rip_iface_config *ic = (void *) iface_patt_find(&cf->patt_list, iface, NULL);
! 734:
! 735: if (ifa && ic)
! 736: {
! 737: if (rip_reconfigure_iface(p, ifa, ic))
! 738: continue;
! 739:
! 740: /* Hard restart */
! 741: log(L_INFO "%s: Restarting interface %s", p->p.name, ifa->iface->name);
! 742: rip_remove_iface(p, ifa);
! 743: rip_add_iface(p, iface, ic);
! 744: }
! 745:
! 746: if (ifa && !ic)
! 747: rip_remove_iface(p, ifa);
! 748:
! 749: if (!ifa && ic)
! 750: rip_add_iface(p, iface, ic);
! 751: }
! 752: }
! 753:
! 754: static void
! 755: rip_if_notify(struct proto *P, unsigned flags, struct iface *iface)
! 756: {
! 757: struct rip_proto *p = (void *) P;
! 758: struct rip_config *cf = (void *) P->cf;
! 759:
! 760: if (iface->flags & IF_IGNORE)
! 761: return;
! 762:
! 763: if (flags & IF_CHANGE_UP)
! 764: {
! 765: struct rip_iface_config *ic = (void *) iface_patt_find(&cf->patt_list, iface, NULL);
! 766:
! 767: if (ic)
! 768: rip_add_iface(p, iface, ic);
! 769:
! 770: return;
! 771: }
! 772:
! 773: struct rip_iface *ifa = rip_find_iface(p, iface);
! 774:
! 775: if (!ifa)
! 776: return;
! 777:
! 778: if (flags & IF_CHANGE_DOWN)
! 779: {
! 780: rip_remove_iface(p, ifa);
! 781: return;
! 782: }
! 783:
! 784: if (flags & IF_CHANGE_MTU)
! 785: rip_iface_update_buffers(ifa);
! 786:
! 787: if (flags & IF_CHANGE_LINK)
! 788: rip_iface_update_state(ifa);
! 789: }
! 790:
! 791:
! 792: /*
! 793: * RIP timer events
! 794: */
! 795:
! 796: /**
! 797: * rip_timer - RIP main timer hook
! 798: * @t: timer
! 799: *
! 800: * The RIP main timer is responsible for routing table maintenance. Invalid or
! 801: * expired routes (&rip_rte) are removed and garbage collection of stale routing
! 802: * table entries (&rip_entry) is done. Changes are propagated to core tables,
! 803: * route reload is also done here. Note that garbage collection uses a maximal
! 804: * GC time, while interfaces maintain an illusion of per-interface GC times in
! 805: * rip_send_response().
! 806: *
! 807: * Keeping incoming routes and the selected outgoing route are two independent
! 808: * functions, therefore after garbage collection some entries now considered
! 809: * invalid (RIP_ENTRY_DUMMY) still may have non-empty list of incoming routes,
! 810: * while some valid entries (representing an outgoing route) may have that list
! 811: * empty.
! 812: *
! 813: * The main timer is not scheduled periodically but it uses the time of the
! 814: * current next event and the minimal interval of any possible event to compute
! 815: * the time of the next run.
! 816: */
! 817: static void
! 818: rip_timer(timer *t)
! 819: {
! 820: struct rip_proto *p = t->data;
! 821: struct rip_config *cf = (void *) (p->p.cf);
! 822: struct rip_iface *ifa;
! 823: struct rip_neighbor *n, *nn;
! 824: struct fib_iterator fit;
! 825: bird_clock_t next = now + MIN(cf->min_timeout_time, cf->max_garbage_time);
! 826: bird_clock_t expires = 0;
! 827:
! 828: TRACE(D_EVENTS, "Main timer fired");
! 829:
! 830: FIB_ITERATE_INIT(&fit, &p->rtable);
! 831:
! 832: loop:
! 833: FIB_ITERATE_START(&p->rtable, &fit, node)
! 834: {
! 835: struct rip_entry *en = (struct rip_entry *) node;
! 836: struct rip_rte *rt, **rp;
! 837: int changed = 0;
! 838:
! 839: /* Checking received routes for timeout and for dead neighbors */
! 840: for (rp = &en->routes; rt = *rp; /* rp = &rt->next */)
! 841: {
! 842: if (!rip_valid_rte(rt) || (rt->expires <= now))
! 843: {
! 844: rip_remove_rte(p, rp);
! 845: changed = 1;
! 846: continue;
! 847: }
! 848:
! 849: next = MIN(next, rt->expires);
! 850: rp = &rt->next;
! 851: }
! 852:
! 853: /* Propagating eventual change */
! 854: if (changed || p->rt_reload)
! 855: {
! 856: /*
! 857: * We have to restart the iteration because there may be a cascade of
! 858: * synchronous events rip_announce_rte() -> nest table change ->
! 859: * rip_rt_notify() -> p->rtable change, invalidating hidden variables.
! 860: */
! 861:
! 862: FIB_ITERATE_PUT_NEXT(&fit, &p->rtable, node);
! 863: rip_announce_rte(p, en);
! 864: goto loop;
! 865: }
! 866:
! 867: /* Checking stale entries for garbage collection timeout */
! 868: if (en->valid == RIP_ENTRY_STALE)
! 869: {
! 870: expires = en->changed + cf->max_garbage_time;
! 871:
! 872: if (expires <= now)
! 873: {
! 874: // TRACE(D_EVENTS, "entry is too old: %I/%d", en->n.prefix, en->n.pxlen);
! 875: en->valid = 0;
! 876: }
! 877: else
! 878: next = MIN(next, expires);
! 879: }
! 880:
! 881: /* Remove empty nodes */
! 882: if (!en->valid && !en->routes)
! 883: {
! 884: FIB_ITERATE_PUT(&fit, node);
! 885: fib_delete(&p->rtable, node);
! 886: goto loop;
! 887: }
! 888: }
! 889: FIB_ITERATE_END(node);
! 890:
! 891: p->rt_reload = 0;
! 892:
! 893: /* Handling neighbor expiration */
! 894: WALK_LIST(ifa, p->iface_list)
! 895: WALK_LIST_DELSAFE(n, nn, ifa->neigh_list)
! 896: if (n->last_seen)
! 897: {
! 898: expires = n->last_seen + n->ifa->cf->timeout_time;
! 899:
! 900: if (expires <= now)
! 901: rip_remove_neighbor(p, n);
! 902: else
! 903: next = MIN(next, expires);
! 904: }
! 905:
! 906: tm_start(p->timer, MAX(next - now, 1));
! 907: }
! 908:
! 909: static inline void
! 910: rip_kick_timer(struct rip_proto *p)
! 911: {
! 912: if (p->timer->expires > (now + 1))
! 913: tm_start(p->timer, 1); /* Or 100 ms */
! 914: }
! 915:
! 916: /**
! 917: * rip_iface_timer - RIP interface timer hook
! 918: * @t: timer
! 919: *
! 920: * RIP interface timers are responsible for scheduling both regular and
! 921: * triggered updates. Fixed, delay-independent period is used for regular
! 922: * updates, while minimal separating interval is enforced for triggered updates.
! 923: * The function also ensures that a new update is not started when the old one
! 924: * is still running.
! 925: */
! 926: static void
! 927: rip_iface_timer(timer *t)
! 928: {
! 929: struct rip_iface *ifa = t->data;
! 930: struct rip_proto *p = ifa->rip;
! 931: bird_clock_t period = ifa->cf->update_time;
! 932:
! 933: if (ifa->cf->passive)
! 934: return;
! 935:
! 936: TRACE(D_EVENTS, "Interface timer fired for %s", ifa->iface->name);
! 937:
! 938: if (ifa->tx_active)
! 939: {
! 940: if (now < (ifa->next_regular + period))
! 941: { tm_start(ifa->timer, 1); return; }
! 942:
! 943: /* We are too late, reset is done by rip_send_table() */
! 944: log(L_WARN "%s: Too slow update on %s, resetting", p->p.name, ifa->iface->name);
! 945: }
! 946:
! 947: if (now >= ifa->next_regular)
! 948: {
! 949: /* Send regular update, set timer for next period (or following one if necessay) */
! 950: TRACE(D_EVENTS, "Sending regular updates for %s", ifa->iface->name);
! 951: rip_send_table(p, ifa, ifa->addr, 0);
! 952: ifa->next_regular += period * (1 + ((now - ifa->next_regular) / period));
! 953: ifa->want_triggered = 0;
! 954: p->triggered = 0;
! 955: }
! 956: else if (ifa->want_triggered && (now >= ifa->next_triggered))
! 957: {
! 958: /* Send triggered update, enforce interval between triggered updates */
! 959: TRACE(D_EVENTS, "Sending triggered updates for %s", ifa->iface->name);
! 960: rip_send_table(p, ifa, ifa->addr, ifa->want_triggered);
! 961: ifa->next_triggered = now + MIN(5, period / 2 + 1);
! 962: ifa->want_triggered = 0;
! 963: p->triggered = 0;
! 964: }
! 965:
! 966: tm_start(ifa->timer, ifa->want_triggered ? 1 : (ifa->next_regular - now));
! 967: }
! 968:
! 969: static inline void
! 970: rip_iface_kick_timer(struct rip_iface *ifa)
! 971: {
! 972: if (ifa->timer->expires > (now + 1))
! 973: tm_start(ifa->timer, 1); /* Or 100 ms */
! 974: }
! 975:
! 976: static void
! 977: rip_trigger_update(struct rip_proto *p)
! 978: {
! 979: if (p->triggered)
! 980: return;
! 981:
! 982: struct rip_iface *ifa;
! 983: WALK_LIST(ifa, p->iface_list)
! 984: {
! 985: /* Interface not active */
! 986: if (! ifa->up)
! 987: continue;
! 988:
! 989: /* Already scheduled */
! 990: if (ifa->want_triggered)
! 991: continue;
! 992:
! 993: TRACE(D_EVENTS, "Scheduling triggered updates for %s", ifa->iface->name);
! 994: ifa->want_triggered = now;
! 995: rip_iface_kick_timer(ifa);
! 996: }
! 997:
! 998: p->triggered = 1;
! 999: }
! 1000:
! 1001:
! 1002: /*
! 1003: * RIP protocol glue
! 1004: */
! 1005:
! 1006: static struct ea_list *
! 1007: rip_prepare_attrs(struct linpool *pool, ea_list *next, u8 metric, u16 tag)
! 1008: {
! 1009: struct ea_list *l = lp_alloc(pool, sizeof(struct ea_list) + 2 * sizeof(eattr));
! 1010:
! 1011: l->next = next;
! 1012: l->flags = EALF_SORTED;
! 1013: l->count = 2;
! 1014:
! 1015: l->attrs[0].id = EA_RIP_METRIC;
! 1016: l->attrs[0].flags = 0;
! 1017: l->attrs[0].type = EAF_TYPE_INT | EAF_TEMP;
! 1018: l->attrs[0].u.data = metric;
! 1019:
! 1020: l->attrs[1].id = EA_RIP_TAG;
! 1021: l->attrs[1].flags = 0;
! 1022: l->attrs[1].type = EAF_TYPE_INT | EAF_TEMP;
! 1023: l->attrs[1].u.data = tag;
! 1024:
! 1025: return l;
! 1026: }
! 1027:
! 1028: static int
! 1029: rip_import_control(struct proto *P UNUSED, struct rte **rt, struct ea_list **attrs, struct linpool *pool)
! 1030: {
! 1031: /* Prepare attributes with initial values */
! 1032: if ((*rt)->attrs->source != RTS_RIP)
! 1033: *attrs = rip_prepare_attrs(pool, *attrs, 1, 0);
! 1034:
! 1035: return 0;
! 1036: }
! 1037:
! 1038: static int
! 1039: rip_reload_routes(struct proto *P)
! 1040: {
! 1041: struct rip_proto *p = (struct rip_proto *) P;
! 1042:
! 1043: if (p->rt_reload)
! 1044: return 1;
! 1045:
! 1046: TRACE(D_EVENTS, "Scheduling route reload");
! 1047: p->rt_reload = 1;
! 1048: rip_kick_timer(p);
! 1049:
! 1050: return 1;
! 1051: }
! 1052:
! 1053: static struct ea_list *
! 1054: rip_make_tmp_attrs(struct rte *rt, struct linpool *pool)
! 1055: {
! 1056: return rip_prepare_attrs(pool, NULL, rt->u.rip.metric, rt->u.rip.tag);
! 1057: }
! 1058:
! 1059: static void
! 1060: rip_store_tmp_attrs(struct rte *rt, struct ea_list *attrs)
! 1061: {
! 1062: rt->u.rip.metric = ea_get_int(attrs, EA_RIP_METRIC, 1);
! 1063: rt->u.rip.tag = ea_get_int(attrs, EA_RIP_TAG, 0);
! 1064: }
! 1065:
! 1066: static int
! 1067: rip_rte_better(struct rte *new, struct rte *old)
! 1068: {
! 1069: return new->u.rip.metric < old->u.rip.metric;
! 1070: }
! 1071:
! 1072: static int
! 1073: rip_rte_same(struct rte *new, struct rte *old)
! 1074: {
! 1075: return ((new->u.rip.metric == old->u.rip.metric) &&
! 1076: (new->u.rip.tag == old->u.rip.tag) &&
! 1077: (new->u.rip.from == old->u.rip.from));
! 1078: }
! 1079:
! 1080:
! 1081: static struct proto *
! 1082: rip_init(struct proto_config *cfg)
! 1083: {
! 1084: struct proto *P = proto_new(cfg, sizeof(struct rip_proto));
! 1085:
! 1086: P->accept_ra_types = RA_OPTIMAL;
! 1087: P->if_notify = rip_if_notify;
! 1088: P->rt_notify = rip_rt_notify;
! 1089: P->neigh_notify = rip_neigh_notify;
! 1090: P->import_control = rip_import_control;
! 1091: P->reload_routes = rip_reload_routes;
! 1092: P->make_tmp_attrs = rip_make_tmp_attrs;
! 1093: P->store_tmp_attrs = rip_store_tmp_attrs;
! 1094: P->rte_better = rip_rte_better;
! 1095: P->rte_same = rip_rte_same;
! 1096:
! 1097: return P;
! 1098: }
! 1099:
! 1100: static int
! 1101: rip_start(struct proto *P)
! 1102: {
! 1103: struct rip_proto *p = (void *) P;
! 1104: struct rip_config *cf = (void *) (P->cf);
! 1105:
! 1106: init_list(&p->iface_list);
! 1107: fib_init(&p->rtable, P->pool, sizeof(struct rip_entry), 0, rip_init_entry);
! 1108: p->rte_slab = sl_new(P->pool, sizeof(struct rip_rte));
! 1109: p->timer = tm_new_set(P->pool, rip_timer, p, 0, 0);
! 1110:
! 1111: p->ecmp = cf->ecmp;
! 1112: p->infinity = cf->infinity;
! 1113: p->triggered = 0;
! 1114:
! 1115: p->log_pkt_tbf = (struct tbf){ .rate = 1, .burst = 5 };
! 1116: p->log_rte_tbf = (struct tbf){ .rate = 4, .burst = 20 };
! 1117:
! 1118: tm_start(p->timer, MIN(cf->min_timeout_time, cf->max_garbage_time));
! 1119:
! 1120: return PS_UP;
! 1121: }
! 1122:
! 1123: static int
! 1124: rip_reconfigure(struct proto *P, struct proto_config *c)
! 1125: {
! 1126: struct rip_proto *p = (void *) P;
! 1127: struct rip_config *new = (void *) c;
! 1128: // struct rip_config *old = (void *) (P->cf);
! 1129:
! 1130: if (new->infinity != p->infinity)
! 1131: return 0;
! 1132:
! 1133: TRACE(D_EVENTS, "Reconfiguring");
! 1134:
! 1135: p->p.cf = c;
! 1136: p->ecmp = new->ecmp;
! 1137: rip_reconfigure_ifaces(p, new);
! 1138:
! 1139: p->rt_reload = 1;
! 1140: rip_kick_timer(p);
! 1141:
! 1142: return 1;
! 1143: }
! 1144:
! 1145: static void
! 1146: rip_get_route_info(rte *rte, byte *buf, ea_list *attrs UNUSED)
! 1147: {
! 1148: buf += bsprintf(buf, " (%d/%d)", rte->pref, rte->u.rip.metric);
! 1149:
! 1150: if (rte->u.rip.tag)
! 1151: bsprintf(buf, " [%04x]", rte->u.rip.tag);
! 1152: }
! 1153:
! 1154: static int
! 1155: rip_get_attr(eattr *a, byte *buf, int buflen UNUSED)
! 1156: {
! 1157: switch (a->id)
! 1158: {
! 1159: case EA_RIP_METRIC:
! 1160: bsprintf(buf, "metric: %d", a->u.data);
! 1161: return GA_FULL;
! 1162:
! 1163: case EA_RIP_TAG:
! 1164: bsprintf(buf, "tag: %04x", a->u.data);
! 1165: return GA_FULL;
! 1166:
! 1167: default:
! 1168: return GA_UNKNOWN;
! 1169: }
! 1170: }
! 1171:
! 1172: void
! 1173: rip_show_interfaces(struct proto *P, char *iff)
! 1174: {
! 1175: struct rip_proto *p = (void *) P;
! 1176: struct rip_iface *ifa = NULL;
! 1177: struct rip_neighbor *n = NULL;
! 1178:
! 1179: if (p->p.proto_state != PS_UP)
! 1180: {
! 1181: cli_msg(-1021, "%s: is not up", p->p.name);
! 1182: cli_msg(0, "");
! 1183: return;
! 1184: }
! 1185:
! 1186: cli_msg(-1021, "%s:", p->p.name);
! 1187: cli_msg(-1021, "%-10s %-6s %6s %6s %6s",
! 1188: "Interface", "State", "Metric", "Nbrs", "Timer");
! 1189:
! 1190: WALK_LIST(ifa, p->iface_list)
! 1191: {
! 1192: if (iff && !patmatch(iff, ifa->iface->name))
! 1193: continue;
! 1194:
! 1195: int nbrs = 0;
! 1196: WALK_LIST(n, ifa->neigh_list)
! 1197: if (n->last_seen)
! 1198: nbrs++;
! 1199:
! 1200: int timer = MAX(ifa->next_regular - now, 0);
! 1201: cli_msg(-1021, "%-10s %-6s %6u %6u %6u",
! 1202: ifa->iface->name, (ifa->up ? "Up" : "Down"), ifa->cf->metric, nbrs, timer);
! 1203: }
! 1204:
! 1205: cli_msg(0, "");
! 1206: }
! 1207:
! 1208: void
! 1209: rip_show_neighbors(struct proto *P, char *iff)
! 1210: {
! 1211: struct rip_proto *p = (void *) P;
! 1212: struct rip_iface *ifa = NULL;
! 1213: struct rip_neighbor *n = NULL;
! 1214:
! 1215: if (p->p.proto_state != PS_UP)
! 1216: {
! 1217: cli_msg(-1022, "%s: is not up", p->p.name);
! 1218: cli_msg(0, "");
! 1219: return;
! 1220: }
! 1221:
! 1222: cli_msg(-1022, "%s:", p->p.name);
! 1223: cli_msg(-1022, "%-25s %-10s %6s %6s %6s",
! 1224: "IP address", "Interface", "Metric", "Routes", "Seen");
! 1225:
! 1226: WALK_LIST(ifa, p->iface_list)
! 1227: {
! 1228: if (iff && !patmatch(iff, ifa->iface->name))
! 1229: continue;
! 1230:
! 1231: WALK_LIST(n, ifa->neigh_list)
! 1232: {
! 1233: if (!n->last_seen)
! 1234: continue;
! 1235:
! 1236: int timer = now - n->last_seen;
! 1237: cli_msg(-1022, "%-25I %-10s %6u %6u %6u",
! 1238: n->nbr->addr, ifa->iface->name, ifa->cf->metric, n->uc, timer);
! 1239: }
! 1240: }
! 1241:
! 1242: cli_msg(0, "");
! 1243: }
! 1244:
! 1245: static void
! 1246: rip_dump(struct proto *P)
! 1247: {
! 1248: struct rip_proto *p = (struct rip_proto *) P;
! 1249: struct rip_iface *ifa;
! 1250: int i;
! 1251:
! 1252: i = 0;
! 1253: FIB_WALK(&p->rtable, e)
! 1254: {
! 1255: struct rip_entry *en = (struct rip_entry *) e;
! 1256: debug("RIP: entry #%d: %I/%d via %I dev %s valid %d metric %d age %d s\n",
! 1257: i++, en->n.prefix, en->n.pxlen, en->next_hop, en->iface->name,
! 1258: en->valid, en->metric, now - en->changed);
! 1259: }
! 1260: FIB_WALK_END;
! 1261:
! 1262: i = 0;
! 1263: WALK_LIST(ifa, p->iface_list)
! 1264: {
! 1265: debug("RIP: interface #%d: %s, %I, up = %d, busy = %d\n",
! 1266: i++, ifa->iface->name, ifa->sk ? ifa->sk->daddr : IPA_NONE,
! 1267: ifa->up, ifa->tx_active);
! 1268: }
! 1269: }
! 1270:
! 1271:
! 1272: struct protocol proto_rip = {
! 1273: .name = "RIP",
! 1274: .template = "rip%d",
! 1275: .attr_class = EAP_RIP,
! 1276: .preference = DEF_PREF_RIP,
! 1277: .config_size = sizeof(struct rip_config),
! 1278: .init = rip_init,
! 1279: .dump = rip_dump,
! 1280: .start = rip_start,
! 1281: .reconfigure = rip_reconfigure,
! 1282: .get_route_info = rip_get_route_info,
! 1283: .get_attr = rip_get_attr
! 1284: };
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>