File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / bird2 / proto / static / static.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Oct 21 16:03:57 2019 UTC (5 years, 5 months ago) by misho
Branches: bird2, MAIN
CVS tags: v2_0_7p0, HEAD
bird2 ver 2.0.7

    1: /*
    2:  *	BIRD -- Static Route Generator
    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: Static
   11:  *
   12:  * The Static protocol is implemented in a straightforward way. It keeps a list
   13:  * of static routes. Routes of dest RTD_UNICAST have associated sticky node in
   14:  * the neighbor cache to be notified about gaining or losing the neighbor and
   15:  * about interface-related events (e.g. link down). They may also have a BFD
   16:  * request if associated with a BFD session. When a route is notified,
   17:  * static_decide() is used to see whether the route activeness is changed. In
   18:  * such case, the route is marked as dirty and scheduled to be announced or
   19:  * withdrawn, which is done asynchronously from event hook. Routes of other
   20:  * types (e.g. black holes) are announced all the time.
   21:  *
   22:  * Multipath routes are a bit tricky. To represent additional next hops, dummy
   23:  * static_route nodes are used, which are chained using @mp_next field and link
   24:  * to the master node by @mp_head field. Each next hop has a separate neighbor
   25:  * entry and an activeness state, but the master node is used for most purposes.
   26:  * Note that most functions DO NOT accept dummy nodes as arguments.
   27:  *
   28:  * The only other thing worth mentioning is that when asked for reconfiguration,
   29:  * Static not only compares the two configurations, but it also calculates
   30:  * difference between the lists of static routes and it just inserts the newly
   31:  * added routes, removes the obsolete ones and reannounces changed ones.
   32:  */
   33: 
   34: #undef LOCAL_DEBUG
   35: 
   36: #include <stdlib.h>
   37: 
   38: #include "nest/bird.h"
   39: #include "nest/iface.h"
   40: #include "nest/protocol.h"
   41: #include "nest/route.h"
   42: #include "nest/cli.h"
   43: #include "conf/conf.h"
   44: #include "filter/filter.h"
   45: #include "lib/string.h"
   46: #include "lib/alloca.h"
   47: 
   48: #include "static.h"
   49: 
   50: static linpool *static_lp;
   51: 
   52: static void
   53: static_announce_rte(struct static_proto *p, struct static_route *r)
   54: {
   55:   rta *a = allocz(RTA_MAX_SIZE);
   56:   a->src = p->p.main_source;
   57:   a->source = RTS_STATIC;
   58:   a->scope = SCOPE_UNIVERSE;
   59:   a->dest = r->dest;
   60: 
   61:   if (r->dest == RTD_UNICAST)
   62:   {
   63:     struct static_route *r2;
   64:     struct nexthop *nhs = NULL;
   65: 
   66:     for (r2 = r; r2; r2 = r2->mp_next)
   67:     {
   68:       if (!r2->active)
   69: 	continue;
   70: 
   71:       struct nexthop *nh = allocz(NEXTHOP_MAX_SIZE);
   72:       nh->gw = r2->via;
   73:       nh->iface = r2->neigh->iface;
   74:       nh->flags = r2->onlink ? RNF_ONLINK : 0;
   75:       nh->weight = r2->weight;
   76:       if (r2->mls)
   77:       {
   78: 	nh->labels = r2->mls->len;
   79: 	memcpy(nh->label, r2->mls->stack, r2->mls->len * sizeof(u32));
   80:       }
   81: 
   82:       nexthop_insert(&nhs, nh);
   83:     }
   84: 
   85:     if (!nhs)
   86:       goto withdraw;
   87: 
   88:     nexthop_link(a, nhs);
   89:   }
   90: 
   91:   if (r->dest == RTDX_RECURSIVE)
   92:   {
   93:     rtable *tab = ipa_is_ip4(r->via) ? p->igp_table_ip4 : p->igp_table_ip6;
   94:     rta_set_recursive_next_hop(p->p.main_channel->table, a, tab, r->via, IPA_NONE, r->mls);
   95:   }
   96: 
   97:   /* Already announced */
   98:   if (r->state == SRS_CLEAN)
   99:     return;
  100: 
  101:   /* We skip rta_lookup() here */
  102:   rte *e = rte_get_temp(a);
  103:   e->pflags = 0;
  104: 
  105:   if (r->cmds)
  106:     f_eval_rte(r->cmds, &e, static_lp);
  107: 
  108:   rte_update(&p->p, r->net, e);
  109:   r->state = SRS_CLEAN;
  110: 
  111:   if (r->cmds)
  112:     lp_flush(static_lp);
  113: 
  114:   return;
  115: 
  116: withdraw:
  117:   if (r->state == SRS_DOWN)
  118:     return;
  119: 
  120:   rte_update(&p->p, r->net, NULL);
  121:   r->state = SRS_DOWN;
  122: }
  123: 
  124: static void
  125: static_mark_rte(struct static_proto *p, struct static_route *r)
  126: {
  127:   if (r->state == SRS_DIRTY)
  128:     return;
  129: 
  130:   r->state = SRS_DIRTY;
  131:   BUFFER_PUSH(p->marked) = r;
  132: 
  133:   if (!ev_active(p->event))
  134:     ev_schedule(p->event);
  135: }
  136: 
  137: static void
  138: static_announce_marked(void *P)
  139: {
  140:   struct static_proto *p = P;
  141: 
  142:   BUFFER_WALK(p->marked, r)
  143:     static_announce_rte(P, r);
  144: 
  145:   BUFFER_FLUSH(p->marked);
  146: }
  147: 
  148: static void
  149: static_bfd_notify(struct bfd_request *req);
  150: 
  151: static void
  152: static_update_bfd(struct static_proto *p, struct static_route *r)
  153: {
  154:   /* The @r is a RTD_UNICAST next hop, may be a dummy node */
  155: 
  156:   struct neighbor *nb = r->neigh;
  157:   int bfd_up = (nb->scope > 0) && r->use_bfd;
  158: 
  159:   if (bfd_up && !r->bfd_req)
  160:   {
  161:     // ip_addr local = ipa_nonzero(r->local) ? r->local : nb->ifa->ip;
  162:     r->bfd_req = bfd_request_session(p->p.pool, r->via, nb->ifa->ip,
  163: 				     nb->iface, p->p.vrf,
  164: 				     static_bfd_notify, r);
  165:   }
  166: 
  167:   if (!bfd_up && r->bfd_req)
  168:   {
  169:     rfree(r->bfd_req);
  170:     r->bfd_req = NULL;
  171:   }
  172: }
  173: 
  174: static int
  175: static_decide(struct static_proto *p, struct static_route *r)
  176: {
  177:   /* The @r is a RTD_UNICAST next hop, may be a dummy node */
  178: 
  179:   struct static_config *cf = (void *) p->p.cf;
  180:   uint old_active = r->active;
  181: 
  182:   if (r->neigh->scope < 0)
  183:     goto fail;
  184: 
  185:   if (cf->check_link && !(r->neigh->iface->flags & IF_LINK_UP))
  186:     goto fail;
  187: 
  188:   if (r->bfd_req && (r->bfd_req->state != BFD_STATE_UP))
  189:     goto fail;
  190: 
  191:   r->active = 1;
  192:   return !old_active;
  193: 
  194: fail:
  195:   r->active = 0;
  196:   return old_active;
  197: }
  198: 
  199: static void
  200: static_add_rte(struct static_proto *p, struct static_route *r)
  201: {
  202:   if (r->dest == RTD_UNICAST)
  203:   {
  204:     struct static_route *r2;
  205:     struct neighbor *n;
  206: 
  207:     for (r2 = r; r2; r2 = r2->mp_next)
  208:     {
  209:       n = neigh_find(&p->p, r2->via, r2->iface, NEF_STICKY |
  210: 		     (r2->onlink ? NEF_ONLINK : 0) |
  211: 		     (ipa_zero(r2->via) ? NEF_IFACE : 0));
  212: 
  213:       if (!n)
  214:       {
  215: 	log(L_WARN "Invalid next hop %I of static route %N", r2->via, r2->net);
  216: 	continue;
  217:       }
  218: 
  219:       r2->neigh = n;
  220:       r2->chain = n->data;
  221:       n->data = r2;
  222: 
  223:       static_update_bfd(p, r2);
  224:       static_decide(p, r2);
  225:     }
  226:   }
  227: 
  228:   static_announce_rte(p, r);
  229: }
  230: 
  231: static void
  232: static_reset_rte(struct static_proto *p UNUSED, struct static_route *r)
  233: {
  234:   struct static_route *r2;
  235: 
  236:   for (r2 = r; r2; r2 = r2->mp_next)
  237:   {
  238:     r2->neigh = NULL;
  239:     r2->chain = NULL;
  240: 
  241:     r2->state = 0;
  242:     r2->active = 0;
  243: 
  244:     rfree(r2->bfd_req);
  245:     r2->bfd_req = NULL;
  246:   }
  247: }
  248: 
  249: static void
  250: static_remove_rte(struct static_proto *p, struct static_route *r)
  251: {
  252:   if (r->state)
  253:     rte_update(&p->p, r->net, NULL);
  254: 
  255:   static_reset_rte(p, r);
  256: }
  257: 
  258: 
  259: static inline int
  260: static_same_dest(struct static_route *x, struct static_route *y)
  261: {
  262:   if (x->dest != y->dest)
  263:     return 0;
  264: 
  265:   switch (x->dest)
  266:   {
  267:   case RTD_UNICAST:
  268:     for (; x && y; x = x->mp_next, y = y->mp_next)
  269:     {
  270:       if (!ipa_equal(x->via, y->via) ||
  271: 	  (x->iface != y->iface) ||
  272: 	  (x->onlink != y->onlink) ||
  273: 	  (x->weight != y->weight) ||
  274: 	  (x->use_bfd != y->use_bfd) ||
  275: 	  (!x->mls != !y->mls) ||
  276: 	  ((x->mls) && (y->mls) && (x->mls->len != y->mls->len)))
  277: 	return 0;
  278: 
  279:       if (!x->mls)
  280: 	continue;
  281: 
  282:       for (uint i = 0; i < x->mls->len; i++)
  283: 	if (x->mls->stack[i] != y->mls->stack[i])
  284: 	  return 0;
  285:     }
  286:     return !x && !y;
  287: 
  288:   case RTDX_RECURSIVE:
  289:     if (!ipa_equal(x->via, y->via) ||
  290: 	(!x->mls != !y->mls) ||
  291: 	((x->mls) && (y->mls) && (x->mls->len != y->mls->len)))
  292:       return 0;
  293: 
  294:     if (!x->mls)
  295:       return 1;
  296: 
  297:     for (uint i = 0; i < x->mls->len; i++)
  298:       if (x->mls->stack[i] != y->mls->stack[i])
  299: 	return 0;
  300: 
  301:     return 1;
  302: 
  303:   default:
  304:     return 1;
  305:   }
  306: }
  307: 
  308: static inline int
  309: static_same_rte(struct static_route *or, struct static_route *nr)
  310: {
  311:   /* Note that i_same() requires arguments in (new, old) order */
  312:   return static_same_dest(or, nr) && f_same(nr->cmds, or->cmds);
  313: }
  314: 
  315: static void
  316: static_reconfigure_rte(struct static_proto *p, struct static_route *or, struct static_route *nr)
  317: {
  318:   if ((or->state == SRS_CLEAN) && !static_same_rte(or, nr))
  319:     nr->state = SRS_DIRTY;
  320:   else
  321:     nr->state = or->state;
  322: 
  323:   static_add_rte(p, nr);
  324:   static_reset_rte(p, or);
  325: }
  326: 
  327: 
  328: static void
  329: static_neigh_notify(struct neighbor *n)
  330: {
  331:   struct static_proto *p = (void *) n->proto;
  332:   struct static_route *r;
  333: 
  334:   DBG("Static: neighbor notify for %I: iface %p\n", n->addr, n->iface);
  335:   for (r = n->data; r; r = r->chain)
  336:   {
  337:     static_update_bfd(p, r);
  338: 
  339:     if (static_decide(p, r))
  340:       static_mark_rte(p, r->mp_head);
  341:   }
  342: }
  343: 
  344: static void
  345: static_bfd_notify(struct bfd_request *req)
  346: {
  347:   struct static_route *r = req->data;
  348:   struct static_proto *p = (void *) r->neigh->proto;
  349: 
  350:   // if (req->down) TRACE(D_EVENTS, "BFD session down for nbr %I on %s", XXXX);
  351: 
  352:   if (static_decide(p, r))
  353:     static_mark_rte(p, r->mp_head);
  354: }
  355: 
  356: static int
  357: static_rte_mergable(rte *pri UNUSED, rte *sec UNUSED)
  358: {
  359:   return 1;
  360: }
  361: 
  362: 
  363: static void
  364: static_postconfig(struct proto_config *CF)
  365: {
  366:   struct static_config *cf = (void *) CF;
  367:   struct static_route *r;
  368: 
  369:   if (EMPTY_LIST(CF->channels))
  370:     cf_error("Channel not specified");
  371: 
  372:   struct channel_config *cc = proto_cf_main_channel(CF);
  373: 
  374:   if (!cf->igp_table_ip4)
  375:     cf->igp_table_ip4 = (cc->table->addr_type == NET_IP4) ?
  376:       cc->table : cf->c.global->def_tables[NET_IP4];
  377: 
  378:   if (!cf->igp_table_ip6)
  379:     cf->igp_table_ip6 = (cc->table->addr_type == NET_IP6) ?
  380:       cc->table : cf->c.global->def_tables[NET_IP6];
  381: 
  382:   WALK_LIST(r, cf->routes)
  383:     if (r->net && (r->net->type != CF->net_type))
  384:       cf_error("Route %N incompatible with channel type", r->net);
  385: }
  386: 
  387: static struct proto *
  388: static_init(struct proto_config *CF)
  389: {
  390:   struct proto *P = proto_new(CF);
  391:   struct static_proto *p = (void *) P;
  392:   struct static_config *cf = (void *) CF;
  393: 
  394:   P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF));
  395: 
  396:   P->neigh_notify = static_neigh_notify;
  397:   P->rte_mergable = static_rte_mergable;
  398: 
  399:   if (cf->igp_table_ip4)
  400:     p->igp_table_ip4 = cf->igp_table_ip4->table;
  401: 
  402:   if (cf->igp_table_ip6)
  403:     p->igp_table_ip6 = cf->igp_table_ip6->table;
  404: 
  405:   return P;
  406: }
  407: 
  408: static int
  409: static_start(struct proto *P)
  410: {
  411:   struct static_proto *p = (void *) P;
  412:   struct static_config *cf = (void *) P->cf;
  413:   struct static_route *r;
  414: 
  415:   if (!static_lp)
  416:     static_lp = lp_new(&root_pool, LP_GOOD_SIZE(1024));
  417: 
  418:   if (p->igp_table_ip4)
  419:     rt_lock_table(p->igp_table_ip4);
  420: 
  421:   if (p->igp_table_ip6)
  422:     rt_lock_table(p->igp_table_ip6);
  423: 
  424:   p->event = ev_new_init(p->p.pool, static_announce_marked, p);
  425: 
  426:   BUFFER_INIT(p->marked, p->p.pool, 4);
  427: 
  428:   /* We have to go UP before routes could be installed */
  429:   proto_notify_state(P, PS_UP);
  430: 
  431:   WALK_LIST(r, cf->routes)
  432:     static_add_rte(p, r);
  433: 
  434:   return PS_UP;
  435: }
  436: 
  437: static int
  438: static_shutdown(struct proto *P)
  439: {
  440:   struct static_proto *p = (void *) P;
  441:   struct static_config *cf = (void *) P->cf;
  442:   struct static_route *r;
  443: 
  444:   /* Just reset the flag, the routes will be flushed by the nest */
  445:   WALK_LIST(r, cf->routes)
  446:     static_reset_rte(p, r);
  447: 
  448:   return PS_DOWN;
  449: }
  450: 
  451: static void
  452: static_cleanup(struct proto *P)
  453: {
  454:   struct static_proto *p = (void *) P;
  455: 
  456:   if (p->igp_table_ip4)
  457:     rt_unlock_table(p->igp_table_ip4);
  458: 
  459:   if (p->igp_table_ip6)
  460:     rt_unlock_table(p->igp_table_ip6);
  461: }
  462: 
  463: static void
  464: static_dump_rte(struct static_route *r)
  465: {
  466:   debug("%-1N: ", r->net);
  467:   if (r->dest == RTD_UNICAST)
  468:     if (r->iface && ipa_zero(r->via))
  469:       debug("dev %s\n", r->iface->name);
  470:     else
  471:       debug("via %I%J\n", r->via, r->iface);
  472:   else
  473:     debug("rtd %d\n", r->dest);
  474: }
  475: 
  476: static void
  477: static_dump(struct proto *P)
  478: {
  479:   struct static_config *c = (void *) P->cf;
  480:   struct static_route *r;
  481: 
  482:   debug("Static routes:\n");
  483:   WALK_LIST(r, c->routes)
  484:     static_dump_rte(r);
  485: }
  486: 
  487: #define IGP_TABLE(cf, sym) ((cf)->igp_table_##sym ? (cf)->igp_table_##sym ->table : NULL )
  488: 
  489: static inline int
  490: static_cmp_rte(const void *X, const void *Y)
  491: {
  492:   struct static_route *x = *(void **)X, *y = *(void **)Y;
  493:   return net_compare(x->net, y->net);
  494: }
  495: 
  496: static int
  497: static_reconfigure(struct proto *P, struct proto_config *CF)
  498: {
  499:   struct static_proto *p = (void *) P;
  500:   struct static_config *o = (void *) P->cf;
  501:   struct static_config *n = (void *) CF;
  502:   struct static_route *r, *r2, *or, *nr;
  503: 
  504:   /* Check change in IGP tables */
  505:   if ((IGP_TABLE(o, ip4) != IGP_TABLE(n, ip4)) ||
  506:       (IGP_TABLE(o, ip6) != IGP_TABLE(n, ip6)))
  507:     return 0;
  508: 
  509:   if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)))
  510:     return 0;
  511: 
  512:   p->p.cf = CF;
  513: 
  514:   /* Reset route lists in neighbor entries */
  515:   WALK_LIST(r, o->routes)
  516:     for (r2 = r; r2; r2 = r2->mp_next)
  517:       if (r2->neigh)
  518: 	r2->neigh->data = NULL;
  519: 
  520:   /* Reconfigure initial matching sequence */
  521:   for (or = HEAD(o->routes), nr = HEAD(n->routes);
  522:        NODE_VALID(or) && NODE_VALID(nr) && net_equal(or->net, nr->net);
  523:        or = NODE_NEXT(or), nr = NODE_NEXT(nr))
  524:     static_reconfigure_rte(p, or, nr);
  525: 
  526:   if (!NODE_VALID(or) && !NODE_VALID(nr))
  527:     return 1;
  528: 
  529:   /* Reconfigure remaining routes, sort them to find matching pairs */
  530:   struct static_route *or2, *nr2, **orbuf, **nrbuf;
  531:   uint ornum = 0, nrnum = 0, orpos = 0, nrpos = 0, i;
  532: 
  533:   for (or2 = or; NODE_VALID(or2); or2 = NODE_NEXT(or2))
  534:     ornum++;
  535: 
  536:   for (nr2 = nr; NODE_VALID(nr2); nr2 = NODE_NEXT(nr2))
  537:     nrnum++;
  538: 
  539:   orbuf = xmalloc(ornum * sizeof(void *));
  540:   nrbuf = xmalloc(nrnum * sizeof(void *));
  541: 
  542:   for (i = 0, or2 = or; i < ornum; i++, or2 = NODE_NEXT(or2))
  543:     orbuf[i] = or2;
  544: 
  545:   for (i = 0, nr2 = nr; i < nrnum; i++, nr2 = NODE_NEXT(nr2))
  546:     nrbuf[i] = nr2;
  547: 
  548:   qsort(orbuf, ornum, sizeof(struct static_route *), static_cmp_rte);
  549:   qsort(nrbuf, nrnum, sizeof(struct static_route *), static_cmp_rte);
  550: 
  551:   while ((orpos < ornum) && (nrpos < nrnum))
  552:   {
  553:     int x = net_compare(orbuf[orpos]->net, nrbuf[nrpos]->net);
  554:     if (x < 0)
  555:       static_remove_rte(p, orbuf[orpos++]);
  556:     else if (x > 0)
  557:       static_add_rte(p, nrbuf[nrpos++]);
  558:     else
  559:       static_reconfigure_rte(p, orbuf[orpos++], nrbuf[nrpos++]);
  560:   }
  561: 
  562:   while (orpos < ornum)
  563:     static_remove_rte(p, orbuf[orpos++]);
  564: 
  565:   while (nrpos < nrnum)
  566:     static_add_rte(p, nrbuf[nrpos++]);
  567: 
  568:   xfree(orbuf);
  569:   xfree(nrbuf);
  570: 
  571:   return 1;
  572: }
  573: 
  574: static void
  575: static_copy_config(struct proto_config *dest, struct proto_config *src)
  576: {
  577:   struct static_config *d = (struct static_config *) dest;
  578:   struct static_config *s = (struct static_config *) src;
  579: 
  580:   struct static_route *srt, *snh;
  581: 
  582:   /* Copy route list */
  583:   init_list(&d->routes);
  584:   WALK_LIST(srt, s->routes)
  585:   {
  586:     struct static_route *drt = NULL, *dnh = NULL, **dnp = &drt;
  587: 
  588:     for (snh = srt; snh; snh = snh->mp_next)
  589:     {
  590:       dnh = cfg_alloc(sizeof(struct static_route));
  591:       memcpy(dnh, snh, sizeof(struct static_route));
  592: 
  593:       if (!drt)
  594: 	add_tail(&d->routes, &(dnh->n));
  595: 
  596:       *dnp = dnh;
  597:       dnp = &(dnh->mp_next);
  598: 
  599:       if (snh->mp_head)
  600: 	dnh->mp_head = drt;
  601:     }
  602:   }
  603: }
  604: 
  605: static void
  606: static_show_rt(struct static_route *r)
  607: {
  608:   switch (r->dest)
  609:   {
  610:   case RTD_UNICAST:
  611:   {
  612:     struct static_route *r2;
  613: 
  614:     cli_msg(-1009, "%N", r->net);
  615:     for (r2 = r; r2; r2 = r2->mp_next)
  616:     {
  617:       if (r2->iface && ipa_zero(r2->via))
  618: 	cli_msg(-1009, "\tdev %s%s", r2->iface->name,
  619: 		r2->active ? "" : " (dormant)");
  620:       else
  621: 	cli_msg(-1009, "\tvia %I%J%s%s%s", r2->via, r2->iface,
  622: 		r2->onlink ? " onlink" : "",
  623: 		r2->bfd_req ? " (bfd)" : "",
  624: 		r2->active ? "" : " (dormant)");
  625:     }
  626:     break;
  627:   }
  628: 
  629:   case RTD_NONE:
  630:   case RTD_BLACKHOLE:
  631:   case RTD_UNREACHABLE:
  632:   case RTD_PROHIBIT:
  633:     cli_msg(-1009, "%N\t%s", r->net, rta_dest_names[r->dest]);
  634:     break;
  635: 
  636:   case RTDX_RECURSIVE:
  637:     cli_msg(-1009, "%N\trecursive %I", r->net, r->via);
  638:     break;
  639:   }
  640: }
  641: 
  642: void
  643: static_show(struct proto *P)
  644: {
  645:   struct static_config *c = (void *) P->cf;
  646:   struct static_route *r;
  647: 
  648:   WALK_LIST(r, c->routes)
  649:     static_show_rt(r);
  650:   cli_msg(0, "");
  651: }
  652: 
  653: 
  654: struct protocol proto_static = {
  655:   .name =		"Static",
  656:   .template =		"static%d",
  657:   .class =		PROTOCOL_STATIC,
  658:   .preference =		DEF_PREF_STATIC,
  659:   .channel_mask =	NB_ANY,
  660:   .proto_size =		sizeof(struct static_proto),
  661:   .config_size =	sizeof(struct static_config),
  662:   .postconfig =		static_postconfig,
  663:   .init =		static_init,
  664:   .dump =		static_dump,
  665:   .start =		static_start,
  666:   .shutdown =		static_shutdown,
  667:   .cleanup =		static_cleanup,
  668:   .reconfigure =	static_reconfigure,
  669:   .copy_config =	static_copy_config
  670: };

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>