Annotation of embedaddon/bird/proto/bfd/bfd.c, revision 1.1

1.1     ! misho       1: /*
        !             2:  *     BIRD -- Bidirectional Forwarding Detection (BFD)
        !             3:  *
        !             4:  *     Can be freely distributed and used under the terms of the GNU GPL.
        !             5:  */
        !             6: 
        !             7: /**
        !             8:  * DOC: Bidirectional Forwarding Detection
        !             9:  *
        !            10:  * The BFD protocol is implemented in three files: |bfd.c| containing the
        !            11:  * protocol logic and the protocol glue with BIRD core, |packets.c| handling BFD
        !            12:  * packet processing, RX, TX and protocol sockets. |io.c| then contains generic
        !            13:  * code for the event loop, threads and event sources (sockets, microsecond
        !            14:  * timers). This generic code will be merged to the main BIRD I/O code in the
        !            15:  * future.
        !            16:  *
        !            17:  * The BFD implementation uses a separate thread with an internal event loop for
        !            18:  * handling the protocol logic, which requires high-res and low-latency timing,
        !            19:  * so it is not affected by the rest of BIRD, which has several low-granularity
        !            20:  * hooks in the main loop, uses second-based timers and cannot offer good
        !            21:  * latency. The core of BFD protocol (the code related to BFD sessions,
        !            22:  * interfaces and packets) runs in the BFD thread, while the rest (the code
        !            23:  * related to BFD requests, BFD neighbors and the protocol glue) runs in the
        !            24:  * main thread.
        !            25:  *
        !            26:  * BFD sessions are represented by structure &bfd_session that contains a state
        !            27:  * related to the session and two timers (TX timer for periodic packets and hold
        !            28:  * timer for session timeout). These sessions are allocated from @session_slab
        !            29:  * and are accessible by two hash tables, @session_hash_id (by session ID) and
        !            30:  * @session_hash_ip (by IP addresses of neighbors). Slab and both hashes are in
        !            31:  * the main protocol structure &bfd_proto. The protocol logic related to BFD
        !            32:  * sessions is implemented in internal functions bfd_session_*(), which are
        !            33:  * expected to be called from the context of BFD thread, and external functions
        !            34:  * bfd_add_session(), bfd_remove_session() and bfd_reconfigure_session(), which
        !            35:  * form an interface to the BFD core for the rest and are expected to be called
        !            36:  * from the context of main thread.
        !            37:  *
        !            38:  * Each BFD session has an associated BFD interface, represented by structure
        !            39:  * &bfd_iface. A BFD interface contains a socket used for TX (the one for RX is
        !            40:  * shared in &bfd_proto), an interface configuration and reference counter.
        !            41:  * Compared to interface structures of other protocols, these structures are not
        !            42:  * created and removed based on interface notification events, but according to
        !            43:  * the needs of BFD sessions. When a new session is created, it requests a
        !            44:  * proper BFD interface by function bfd_get_iface(), which either finds an
        !            45:  * existing one in &iface_list (from &bfd_proto) or allocates a new one. When a
        !            46:  * session is removed, an associated iface is discharged by bfd_free_iface().
        !            47:  *
        !            48:  * BFD requests are the external API for the other protocols. When a protocol
        !            49:  * wants a BFD session, it calls bfd_request_session(), which creates a
        !            50:  * structure &bfd_request containing approprite information and an notify hook.
        !            51:  * This structure is a resource associated with the caller's resource pool. When
        !            52:  * a BFD protocol is available, a BFD request is submitted to the protocol, an
        !            53:  * appropriate BFD session is found or created and the request is attached to
        !            54:  * the session. When a session changes state, all attached requests (and related
        !            55:  * protocols) are notified. Note that BFD requests do not depend on BFD protocol
        !            56:  * running. When the BFD protocol is stopped or removed (or not available from
        !            57:  * beginning), related BFD requests are stored in @bfd_wait_list, where waits
        !            58:  * for a new protocol.
        !            59:  *
        !            60:  * BFD neighbors are just a way to statically configure BFD sessions without
        !            61:  * requests from other protocol. Structures &bfd_neighbor are part of BFD
        !            62:  * configuration (like static routes in the static protocol). BFD neighbors are
        !            63:  * handled by BFD protocol like it is a BFD client -- when a BFD neighbor is
        !            64:  * ready, the protocol just creates a BFD request like any other protocol.
        !            65:  *
        !            66:  * The protocol uses a new generic event loop (structure &birdloop) from |io.c|,
        !            67:  * which supports sockets, timers and events like the main loop. Timers
        !            68:  * (structure &timer2) are new microsecond based timers, while sockets and
        !            69:  * events are the same. A birdloop is associated with a thread (field @thread)
        !            70:  * in which event hooks are executed. Most functions for setting event sources
        !            71:  * (like sk_start() or tm2_start()) must be called from the context of that
        !            72:  * thread. Birdloop allows to temporarily acquire the context of that thread for
        !            73:  * the main thread by calling birdloop_enter() and then birdloop_leave(), which
        !            74:  * also ensures mutual exclusion with all event hooks. Note that resources
        !            75:  * associated with a birdloop (like timers) should be attached to the
        !            76:  * independent resource pool, detached from the main resource tree.
        !            77:  *
        !            78:  * There are two kinds of interaction between the BFD core (running in the BFD
        !            79:  * thread) and the rest of BFD (running in the main thread). The first kind are
        !            80:  * configuration calls from main thread to the BFD thread (like bfd_add_session()).
        !            81:  * These calls are synchronous and use birdloop_enter() mechanism for mutual
        !            82:  * exclusion. The second kind is a notification about session changes from the
        !            83:  * BFD thread to the main thread. This is done in an asynchronous way, sesions
        !            84:  * with pending notifications are linked (in the BFD thread) to @notify_list in
        !            85:  * &bfd_proto, and then bfd_notify_hook() in the main thread is activated using
        !            86:  * bfd_notify_kick() and a pipe. The hook then processes scheduled sessions and
        !            87:  * calls hooks from associated BFD requests. This @notify_list (and state fields
        !            88:  * in structure &bfd_session) is protected by a spinlock in &bfd_proto and
        !            89:  * functions bfd_lock_sessions() / bfd_unlock_sessions().
        !            90:  *
        !            91:  * There are few data races (accessing @p->p.debug from TRACE() from the BFD
        !            92:  * thread and accessing some some private fields of %bfd_session from
        !            93:  * bfd_show_sessions() from the main thread, but these are harmless (i hope).
        !            94:  *
        !            95:  * TODO: document functions and access restrictions for fields in BFD structures.
        !            96:  *
        !            97:  * Supported standards:
        !            98:  * - RFC 5880 - main BFD standard
        !            99:  * - RFC 5881 - BFD for IP links
        !           100:  * - RFC 5882 - generic application of BFD
        !           101:  * - RFC 5883 - BFD for multihop paths
        !           102:  */
        !           103: 
        !           104: #include "bfd.h"
        !           105: 
        !           106: 
        !           107: #define HASH_ID_KEY(n)         n->loc_id
        !           108: #define HASH_ID_NEXT(n)                n->next_id
        !           109: #define HASH_ID_EQ(a,b)                a == b
        !           110: #define HASH_ID_FN(k)          k
        !           111: 
        !           112: #define HASH_IP_KEY(n)         n->addr
        !           113: #define HASH_IP_NEXT(n)                n->next_ip
        !           114: #define HASH_IP_EQ(a,b)                ipa_equal(a,b)
        !           115: #define HASH_IP_FN(k)          ipa_hash32(k)
        !           116: 
        !           117: static list bfd_proto_list;
        !           118: static list bfd_wait_list;
        !           119: 
        !           120: const char *bfd_state_names[] = { "AdminDown", "Down", "Init", "Up" };
        !           121: 
        !           122: static void bfd_session_set_min_tx(struct bfd_session *s, u32 val);
        !           123: static struct bfd_iface *bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface);
        !           124: static void bfd_free_iface(struct bfd_iface *ifa);
        !           125: static inline void bfd_notify_kick(struct bfd_proto *p);
        !           126: 
        !           127: 
        !           128: /*
        !           129:  *     BFD sessions
        !           130:  */
        !           131: 
        !           132: static void
        !           133: bfd_session_update_state(struct bfd_session *s, uint state, uint diag)
        !           134: {
        !           135:   struct bfd_proto *p = s->ifa->bfd;
        !           136:   uint old_state = s->loc_state;
        !           137:   int notify;
        !           138: 
        !           139:   if (state == old_state)
        !           140:     return;
        !           141: 
        !           142:   TRACE(D_EVENTS, "Session to %I changed state from %s to %s",
        !           143:        s->addr, bfd_state_names[old_state], bfd_state_names[state]);
        !           144: 
        !           145:   bfd_lock_sessions(p);
        !           146:   s->loc_state = state;
        !           147:   s->loc_diag = diag;
        !           148: 
        !           149:   notify = !NODE_VALID(&s->n);
        !           150:   if (notify)
        !           151:     add_tail(&p->notify_list, &s->n);
        !           152:   bfd_unlock_sessions(p);
        !           153: 
        !           154:   if (state == BFD_STATE_UP)
        !           155:     bfd_session_set_min_tx(s, s->ifa->cf->min_tx_int);
        !           156: 
        !           157:   if (old_state == BFD_STATE_UP)
        !           158:     bfd_session_set_min_tx(s, s->ifa->cf->idle_tx_int);
        !           159: 
        !           160:   if (notify)
        !           161:     bfd_notify_kick(p);
        !           162: }
        !           163: 
        !           164: static void
        !           165: bfd_session_update_tx_interval(struct bfd_session *s)
        !           166: {
        !           167:   u32 tx_int = MAX(s->des_min_tx_int, s->rem_min_rx_int);
        !           168:   u32 tx_int_l = tx_int - (tx_int / 4);         // 75 %
        !           169:   u32 tx_int_h = tx_int - (tx_int / 10); // 90 %
        !           170: 
        !           171:   s->tx_timer->recurrent = tx_int_l;
        !           172:   s->tx_timer->randomize = tx_int_h - tx_int_l;
        !           173: 
        !           174:   /* Do not set timer if no previous event */
        !           175:   if (!s->last_tx)
        !           176:     return;
        !           177: 
        !           178:   /* Set timer relative to last tx_timer event */
        !           179:   tm2_set(s->tx_timer, s->last_tx + tx_int_l);
        !           180: }
        !           181: 
        !           182: static void
        !           183: bfd_session_update_detection_time(struct bfd_session *s, int kick)
        !           184: {
        !           185:   btime timeout = (btime) MAX(s->req_min_rx_int, s->rem_min_tx_int) * s->rem_detect_mult;
        !           186: 
        !           187:   if (kick)
        !           188:     s->last_rx = current_time();
        !           189: 
        !           190:   if (!s->last_rx)
        !           191:     return;
        !           192: 
        !           193:   tm2_set(s->hold_timer, s->last_rx + timeout);
        !           194: }
        !           195: 
        !           196: static void
        !           197: bfd_session_control_tx_timer(struct bfd_session *s, int reset)
        !           198: {
        !           199:   // if (!s->opened) goto stop;
        !           200: 
        !           201:   if (s->passive && (s->rem_id == 0))
        !           202:     goto stop;
        !           203: 
        !           204:   if (s->rem_demand_mode &&
        !           205:       !s->poll_active &&
        !           206:       (s->loc_state == BFD_STATE_UP) &&
        !           207:       (s->rem_state == BFD_STATE_UP))
        !           208:     goto stop;
        !           209: 
        !           210:   if (s->rem_min_rx_int == 0)
        !           211:     goto stop;
        !           212: 
        !           213:   /* So TX timer should run */
        !           214:   if (reset || !tm2_active(s->tx_timer))
        !           215:   {
        !           216:     s->last_tx = 0;
        !           217:     tm2_start(s->tx_timer, 0);
        !           218:   }
        !           219: 
        !           220:   return;
        !           221: 
        !           222:  stop:
        !           223:   tm2_stop(s->tx_timer);
        !           224:   s->last_tx = 0;
        !           225: }
        !           226: 
        !           227: static void
        !           228: bfd_session_request_poll(struct bfd_session *s, u8 request)
        !           229: {
        !           230:   /* Not sure about this, but doing poll in this case does not make sense */
        !           231:   if (s->rem_id == 0)
        !           232:     return;
        !           233: 
        !           234:   s->poll_scheduled |= request;
        !           235: 
        !           236:   if (s->poll_active)
        !           237:     return;
        !           238: 
        !           239:   s->poll_active = s->poll_scheduled;
        !           240:   s->poll_scheduled = 0;
        !           241: 
        !           242:   bfd_session_control_tx_timer(s, 1);
        !           243: }
        !           244: 
        !           245: static void
        !           246: bfd_session_terminate_poll(struct bfd_session *s)
        !           247: {
        !           248:   u8 poll_done = s->poll_active & ~s->poll_scheduled;
        !           249: 
        !           250:   if (poll_done & BFD_POLL_TX)
        !           251:     s->des_min_tx_int = s->des_min_tx_new;
        !           252: 
        !           253:   if (poll_done & BFD_POLL_RX)
        !           254:     s->req_min_rx_int = s->req_min_rx_new;
        !           255: 
        !           256:   s->poll_active = s->poll_scheduled;
        !           257:   s->poll_scheduled = 0;
        !           258: 
        !           259:   /* Timers are updated by caller - bfd_session_process_ctl() */
        !           260: }
        !           261: 
        !           262: void
        !           263: bfd_session_process_ctl(struct bfd_session *s, u8 flags, u32 old_tx_int, u32 old_rx_int)
        !           264: {
        !           265:   if (s->poll_active && (flags & BFD_FLAG_FINAL))
        !           266:     bfd_session_terminate_poll(s);
        !           267: 
        !           268:   if ((s->des_min_tx_int != old_tx_int) || (s->rem_min_rx_int != old_rx_int))
        !           269:     bfd_session_update_tx_interval(s);
        !           270: 
        !           271:   bfd_session_update_detection_time(s, 1);
        !           272: 
        !           273:   /* Update session state */
        !           274:   int next_state = 0;
        !           275:   int diag = BFD_DIAG_NOTHING;
        !           276: 
        !           277:   switch (s->loc_state)
        !           278:   {
        !           279:   case BFD_STATE_ADMIN_DOWN:
        !           280:     return;
        !           281: 
        !           282:   case BFD_STATE_DOWN:
        !           283:     if (s->rem_state == BFD_STATE_DOWN)                next_state = BFD_STATE_INIT;
        !           284:     else if (s->rem_state == BFD_STATE_INIT)   next_state = BFD_STATE_UP;
        !           285:     break;
        !           286: 
        !           287:   case BFD_STATE_INIT:
        !           288:     if (s->rem_state == BFD_STATE_ADMIN_DOWN)  next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN;
        !           289:     else if (s->rem_state >= BFD_STATE_INIT)   next_state = BFD_STATE_UP;
        !           290:     break;
        !           291: 
        !           292:   case BFD_STATE_UP:
        !           293:     if (s->rem_state <= BFD_STATE_DOWN)                next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN;
        !           294:     break;
        !           295:   }
        !           296: 
        !           297:   if (next_state)
        !           298:     bfd_session_update_state(s, next_state, diag);
        !           299: 
        !           300:   bfd_session_control_tx_timer(s, 0);
        !           301: 
        !           302:   if (flags & BFD_FLAG_POLL)
        !           303:     bfd_send_ctl(s->ifa->bfd, s, 1);
        !           304: }
        !           305: 
        !           306: static void
        !           307: bfd_session_timeout(struct bfd_session *s)
        !           308: {
        !           309:   struct bfd_proto *p = s->ifa->bfd;
        !           310: 
        !           311:   TRACE(D_EVENTS, "Session to %I expired", s->addr);
        !           312: 
        !           313:   s->rem_state = BFD_STATE_DOWN;
        !           314:   s->rem_id = 0;
        !           315:   s->rem_min_tx_int = 0;
        !           316:   s->rem_min_rx_int = 1;
        !           317:   s->rem_demand_mode = 0;
        !           318:   s->rem_detect_mult = 0;
        !           319:   s->rx_csn_known = 0;
        !           320: 
        !           321:   s->poll_active = 0;
        !           322:   s->poll_scheduled = 0;
        !           323: 
        !           324:   bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_TIMEOUT);
        !           325: 
        !           326:   bfd_session_control_tx_timer(s, 1);
        !           327: }
        !           328: 
        !           329: static void
        !           330: bfd_session_set_min_tx(struct bfd_session *s, u32 val)
        !           331: {
        !           332:   /* Note that des_min_tx_int <= des_min_tx_new */
        !           333: 
        !           334:   if (val == s->des_min_tx_new)
        !           335:     return;
        !           336: 
        !           337:   s->des_min_tx_new = val;
        !           338: 
        !           339:   /* Postpone timer update if des_min_tx_int increases and the session is up */
        !           340:   if ((s->loc_state != BFD_STATE_UP) || (val < s->des_min_tx_int))
        !           341:   {
        !           342:     s->des_min_tx_int = val;
        !           343:     bfd_session_update_tx_interval(s);
        !           344:   }
        !           345: 
        !           346:   bfd_session_request_poll(s, BFD_POLL_TX);
        !           347: }
        !           348: 
        !           349: static void
        !           350: bfd_session_set_min_rx(struct bfd_session *s, u32 val)
        !           351: {
        !           352:   /* Note that req_min_rx_int >= req_min_rx_new */
        !           353: 
        !           354:   if (val == s->req_min_rx_new)
        !           355:     return;
        !           356: 
        !           357:   s->req_min_rx_new = val;
        !           358: 
        !           359:   /* Postpone timer update if req_min_rx_int decreases and the session is up */
        !           360:   if ((s->loc_state != BFD_STATE_UP) || (val > s->req_min_rx_int))
        !           361:   {
        !           362:     s->req_min_rx_int = val;
        !           363:     bfd_session_update_detection_time(s, 0);
        !           364:   }
        !           365: 
        !           366:   bfd_session_request_poll(s, BFD_POLL_RX);
        !           367: }
        !           368: 
        !           369: struct bfd_session *
        !           370: bfd_find_session_by_id(struct bfd_proto *p, u32 id)
        !           371: {
        !           372:   return HASH_FIND(p->session_hash_id, HASH_ID, id);
        !           373: }
        !           374: 
        !           375: struct bfd_session *
        !           376: bfd_find_session_by_addr(struct bfd_proto *p, ip_addr addr)
        !           377: {
        !           378:   return HASH_FIND(p->session_hash_ip, HASH_IP, addr);
        !           379: }
        !           380: 
        !           381: static void
        !           382: bfd_tx_timer_hook(timer2 *t)
        !           383: {
        !           384:   struct bfd_session *s = t->data;
        !           385: 
        !           386:   s->last_tx = current_time();
        !           387:   bfd_send_ctl(s->ifa->bfd, s, 0);
        !           388: }
        !           389: 
        !           390: static void
        !           391: bfd_hold_timer_hook(timer2 *t)
        !           392: {
        !           393:   bfd_session_timeout(t->data);
        !           394: }
        !           395: 
        !           396: static u32
        !           397: bfd_get_free_id(struct bfd_proto *p)
        !           398: {
        !           399:   u32 id;
        !           400:   for (id = random_u32(); 1; id++)
        !           401:     if (id && !bfd_find_session_by_id(p, id))
        !           402:       break;
        !           403: 
        !           404:   return id;
        !           405: }
        !           406: 
        !           407: static struct bfd_session *
        !           408: bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *iface)
        !           409: {
        !           410:   birdloop_enter(p->loop);
        !           411: 
        !           412:   struct bfd_iface *ifa = bfd_get_iface(p, local, iface);
        !           413: 
        !           414:   struct bfd_session *s = sl_alloc(p->session_slab);
        !           415:   bzero(s, sizeof(struct bfd_session));
        !           416: 
        !           417:   s->addr = addr;
        !           418:   s->ifa = ifa;
        !           419:   s->loc_id = bfd_get_free_id(p);
        !           420: 
        !           421:   HASH_INSERT(p->session_hash_id, HASH_ID, s);
        !           422:   HASH_INSERT(p->session_hash_ip, HASH_IP, s);
        !           423: 
        !           424: 
        !           425:   /* Initialization of state variables - see RFC 5880 6.8.1 */
        !           426:   s->loc_state = BFD_STATE_DOWN;
        !           427:   s->rem_state = BFD_STATE_DOWN;
        !           428:   s->des_min_tx_int = s->des_min_tx_new = ifa->cf->idle_tx_int;
        !           429:   s->req_min_rx_int = s->req_min_rx_new = ifa->cf->min_rx_int;
        !           430:   s->rem_min_rx_int = 1;
        !           431:   s->detect_mult = ifa->cf->multiplier;
        !           432:   s->passive = ifa->cf->passive;
        !           433:   s->tx_csn = random_u32();
        !           434: 
        !           435:   s->tx_timer = tm2_new_init(p->tpool, bfd_tx_timer_hook, s, 0, 0);
        !           436:   s->hold_timer = tm2_new_init(p->tpool, bfd_hold_timer_hook, s, 0, 0);
        !           437:   bfd_session_update_tx_interval(s);
        !           438:   bfd_session_control_tx_timer(s, 1);
        !           439: 
        !           440:   init_list(&s->request_list);
        !           441:   s->last_state_change = now;
        !           442: 
        !           443:   TRACE(D_EVENTS, "Session to %I added", s->addr);
        !           444: 
        !           445:   birdloop_leave(p->loop);
        !           446: 
        !           447:   return s;
        !           448: }
        !           449: 
        !           450: /*
        !           451: static void
        !           452: bfd_open_session(struct bfd_proto *p, struct bfd_session *s, ip_addr local, struct iface *ifa)
        !           453: {
        !           454:   birdloop_enter(p->loop);
        !           455: 
        !           456:   s->opened = 1;
        !           457: 
        !           458:   bfd_session_control_tx_timer(s);
        !           459: 
        !           460:   birdloop_leave(p->loop);
        !           461: }
        !           462: 
        !           463: static void
        !           464: bfd_close_session(struct bfd_proto *p, struct bfd_session *s)
        !           465: {
        !           466:   birdloop_enter(p->loop);
        !           467: 
        !           468:   s->opened = 0;
        !           469: 
        !           470:   bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_PATH_DOWN);
        !           471:   bfd_session_control_tx_timer(s);
        !           472: 
        !           473:   birdloop_leave(p->loop);
        !           474: }
        !           475: */
        !           476: 
        !           477: static void
        !           478: bfd_remove_session(struct bfd_proto *p, struct bfd_session *s)
        !           479: {
        !           480:   ip_addr ip = s->addr;
        !           481: 
        !           482:   /* Caller should ensure that request list is empty */
        !           483: 
        !           484:   birdloop_enter(p->loop);
        !           485: 
        !           486:   /* Remove session from notify list if scheduled for notification */
        !           487:   /* No need for bfd_lock_sessions(), we are already protected by birdloop_enter() */
        !           488:   if (NODE_VALID(&s->n))
        !           489:     rem_node(&s->n);
        !           490: 
        !           491:   bfd_free_iface(s->ifa);
        !           492: 
        !           493:   rfree(s->tx_timer);
        !           494:   rfree(s->hold_timer);
        !           495: 
        !           496:   HASH_REMOVE(p->session_hash_id, HASH_ID, s);
        !           497:   HASH_REMOVE(p->session_hash_ip, HASH_IP, s);
        !           498: 
        !           499:   sl_free(p->session_slab, s);
        !           500: 
        !           501:   TRACE(D_EVENTS, "Session to %I removed", ip);
        !           502: 
        !           503:   birdloop_leave(p->loop);
        !           504: }
        !           505: 
        !           506: static void
        !           507: bfd_reconfigure_session(struct bfd_proto *p, struct bfd_session *s)
        !           508: {
        !           509:   birdloop_enter(p->loop);
        !           510: 
        !           511:   struct bfd_iface_config *cf = s->ifa->cf;
        !           512: 
        !           513:   u32 tx = (s->loc_state == BFD_STATE_UP) ? cf->min_tx_int : cf->idle_tx_int;
        !           514:   bfd_session_set_min_tx(s, tx);
        !           515:   bfd_session_set_min_rx(s, cf->min_rx_int);
        !           516:   s->detect_mult = cf->multiplier;
        !           517:   s->passive = cf->passive;
        !           518: 
        !           519:   bfd_session_control_tx_timer(s, 0);
        !           520: 
        !           521:   birdloop_leave(p->loop);
        !           522: 
        !           523:   TRACE(D_EVENTS, "Session to %I reconfigured", s->addr);
        !           524: }
        !           525: 
        !           526: 
        !           527: /*
        !           528:  *     BFD interfaces
        !           529:  */
        !           530: 
        !           531: static struct bfd_iface_config bfd_default_iface = {
        !           532:   .min_rx_int = BFD_DEFAULT_MIN_RX_INT,
        !           533:   .min_tx_int = BFD_DEFAULT_MIN_TX_INT,
        !           534:   .idle_tx_int = BFD_DEFAULT_IDLE_TX_INT,
        !           535:   .multiplier = BFD_DEFAULT_MULTIPLIER
        !           536: };
        !           537: 
        !           538: static inline struct bfd_iface_config *
        !           539: bfd_find_iface_config(struct bfd_config *cf, struct iface *iface)
        !           540: {
        !           541:   struct bfd_iface_config *ic;
        !           542: 
        !           543:   ic = iface ? (void *) iface_patt_find(&cf->patt_list, iface, NULL) : cf->multihop;
        !           544: 
        !           545:   return ic ? ic : &bfd_default_iface;
        !           546: }
        !           547: 
        !           548: static struct bfd_iface *
        !           549: bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface)
        !           550: {
        !           551:   struct bfd_iface *ifa;
        !           552: 
        !           553:   WALK_LIST(ifa, p->iface_list)
        !           554:     if (ipa_equal(ifa->local, local) && (ifa->iface == iface))
        !           555:       return ifa->uc++, ifa;
        !           556: 
        !           557:   struct bfd_config *cf = (struct bfd_config *) (p->p.cf);
        !           558:   struct bfd_iface_config *ic = bfd_find_iface_config(cf, iface);
        !           559: 
        !           560:   ifa = mb_allocz(p->tpool, sizeof(struct bfd_iface));
        !           561:   ifa->local = local;
        !           562:   ifa->iface = iface;
        !           563:   ifa->cf = ic;
        !           564:   ifa->bfd = p;
        !           565: 
        !           566:   ifa->sk = bfd_open_tx_sk(p, local, iface);
        !           567:   ifa->uc = 1;
        !           568: 
        !           569:   add_tail(&p->iface_list, &ifa->n);
        !           570: 
        !           571:   return ifa;
        !           572: }
        !           573: 
        !           574: static void
        !           575: bfd_free_iface(struct bfd_iface *ifa)
        !           576: {
        !           577:   if (!ifa || --ifa->uc)
        !           578:     return;
        !           579: 
        !           580:   if (ifa->sk)
        !           581:   {
        !           582:     sk_stop(ifa->sk);
        !           583:     rfree(ifa->sk);
        !           584:   }
        !           585: 
        !           586:   rem_node(&ifa->n);
        !           587:   mb_free(ifa);
        !           588: }
        !           589: 
        !           590: static void
        !           591: bfd_reconfigure_iface(struct bfd_proto *p, struct bfd_iface *ifa, struct bfd_config *nc)
        !           592: {
        !           593:   struct bfd_iface_config *nic = bfd_find_iface_config(nc, ifa->iface);
        !           594:   ifa->changed = !!memcmp(nic, ifa->cf, sizeof(struct bfd_iface_config));
        !           595: 
        !           596:   /* This should be probably changed to not access ifa->cf from the BFD thread */
        !           597:   birdloop_enter(p->loop);
        !           598:   ifa->cf = nic;
        !           599:   birdloop_leave(p->loop);
        !           600: }
        !           601: 
        !           602: 
        !           603: /*
        !           604:  *     BFD requests
        !           605:  */
        !           606: 
        !           607: static void
        !           608: bfd_request_notify(struct bfd_request *req, u8 state, u8 diag)
        !           609: {
        !           610:   u8 old_state = req->state;
        !           611: 
        !           612:   if (state == old_state)
        !           613:     return;
        !           614: 
        !           615:   req->state = state;
        !           616:   req->diag = diag;
        !           617:   req->old_state = old_state;
        !           618:   req->down = (old_state == BFD_STATE_UP) && (state == BFD_STATE_DOWN);
        !           619: 
        !           620:   if (req->hook)
        !           621:     req->hook(req);
        !           622: }
        !           623: 
        !           624: static int
        !           625: bfd_add_request(struct bfd_proto *p, struct bfd_request *req)
        !           626: {
        !           627:   struct bfd_session *s = bfd_find_session_by_addr(p, req->addr);
        !           628:   u8 state, diag;
        !           629: 
        !           630:   if (!s)
        !           631:     s = bfd_add_session(p, req->addr, req->local, req->iface);
        !           632: 
        !           633:   rem_node(&req->n);
        !           634:   add_tail(&s->request_list, &req->n);
        !           635:   req->session = s;
        !           636: 
        !           637:   bfd_lock_sessions(p);
        !           638:   state = s->loc_state;
        !           639:   diag = s->loc_diag;
        !           640:   bfd_unlock_sessions(p);
        !           641: 
        !           642:   bfd_request_notify(req, state, diag);
        !           643: 
        !           644:   return 1;
        !           645: }
        !           646: 
        !           647: static void
        !           648: bfd_submit_request(struct bfd_request *req)
        !           649: {
        !           650:   node *n;
        !           651: 
        !           652:   WALK_LIST(n, bfd_proto_list)
        !           653:     if (bfd_add_request(SKIP_BACK(struct bfd_proto, bfd_node, n), req))
        !           654:       return;
        !           655: 
        !           656:   rem_node(&req->n);
        !           657:   add_tail(&bfd_wait_list, &req->n);
        !           658:   req->session = NULL;
        !           659:   bfd_request_notify(req, BFD_STATE_ADMIN_DOWN, 0);
        !           660: }
        !           661: 
        !           662: static void
        !           663: bfd_take_requests(struct bfd_proto *p)
        !           664: {
        !           665:   node *n, *nn;
        !           666: 
        !           667:   WALK_LIST_DELSAFE(n, nn, bfd_wait_list)
        !           668:     bfd_add_request(p, SKIP_BACK(struct bfd_request, n, n));
        !           669: }
        !           670: 
        !           671: static void
        !           672: bfd_drop_requests(struct bfd_proto *p)
        !           673: {
        !           674:   node *n;
        !           675: 
        !           676:   HASH_WALK(p->session_hash_id, next_id, s)
        !           677:   {
        !           678:     /* We assume that p is not in bfd_proto_list */
        !           679:     WALK_LIST_FIRST(n, s->request_list)
        !           680:       bfd_submit_request(SKIP_BACK(struct bfd_request, n, n));
        !           681:   }
        !           682:   HASH_WALK_END;
        !           683: }
        !           684: 
        !           685: static struct resclass bfd_request_class;
        !           686: 
        !           687: struct bfd_request *
        !           688: bfd_request_session(pool *p, ip_addr addr, ip_addr local, struct iface *iface,
        !           689:                    void (*hook)(struct bfd_request *), void *data)
        !           690: {
        !           691:   struct bfd_request *req = ralloc(p, &bfd_request_class);
        !           692: 
        !           693:   /* Hack: self-link req->n, we will call rem_node() on it */
        !           694:   req->n.prev = req->n.next = &req->n;
        !           695: 
        !           696:   req->addr = addr;
        !           697:   req->local = local;
        !           698:   req->iface = iface;
        !           699: 
        !           700:   bfd_submit_request(req);
        !           701: 
        !           702:   req->hook = hook;
        !           703:   req->data = data;
        !           704: 
        !           705:   return req;
        !           706: }
        !           707: 
        !           708: static void
        !           709: bfd_request_free(resource *r)
        !           710: {
        !           711:   struct bfd_request *req = (struct bfd_request *) r;
        !           712:   struct bfd_session *s = req->session;
        !           713: 
        !           714:   rem_node(&req->n);
        !           715: 
        !           716:   /* Remove the session if there is no request for it. Skip that if
        !           717:      inside notify hooks, will be handled by bfd_notify_hook() itself */
        !           718: 
        !           719:   if (s && EMPTY_LIST(s->request_list) && !s->notify_running)
        !           720:     bfd_remove_session(s->ifa->bfd, s);
        !           721: }
        !           722: 
        !           723: static void
        !           724: bfd_request_dump(resource *r)
        !           725: {
        !           726:   struct bfd_request *req = (struct bfd_request *) r;
        !           727: 
        !           728:   debug("(code %p, data %p)\n", req->hook, req->data);
        !           729: }
        !           730: 
        !           731: static struct resclass bfd_request_class = {
        !           732:   "BFD request",
        !           733:   sizeof(struct bfd_request),
        !           734:   bfd_request_free,
        !           735:   bfd_request_dump,
        !           736:   NULL,
        !           737:   NULL
        !           738: };
        !           739: 
        !           740: 
        !           741: /*
        !           742:  *     BFD neighbors
        !           743:  */
        !           744: 
        !           745: static void
        !           746: bfd_neigh_notify(struct neighbor *nb)
        !           747: {
        !           748:   struct bfd_proto *p = (struct bfd_proto *) nb->proto;
        !           749:   struct bfd_neighbor *n = nb->data;
        !           750: 
        !           751:   if (!n)
        !           752:     return;
        !           753: 
        !           754:   if ((nb->scope > 0) && !n->req)
        !           755:   {
        !           756:     ip_addr local = ipa_nonzero(n->local) ? n->local : nb->ifa->ip;
        !           757:     n->req = bfd_request_session(p->p.pool, n->addr, local, nb->iface, NULL, NULL);
        !           758:   }
        !           759: 
        !           760:   if ((nb->scope <= 0) && n->req)
        !           761:   {
        !           762:     rfree(n->req);
        !           763:     n->req = NULL;
        !           764:   }
        !           765: }
        !           766: 
        !           767: static void
        !           768: bfd_start_neighbor(struct bfd_proto *p, struct bfd_neighbor *n)
        !           769: {
        !           770:   n->active = 1;
        !           771: 
        !           772:   if (n->multihop)
        !           773:   {
        !           774:     n->req = bfd_request_session(p->p.pool, n->addr, n->local, NULL, NULL, NULL);
        !           775:     return;
        !           776:   }
        !           777: 
        !           778:   struct neighbor *nb = neigh_find2(&p->p, &n->addr, n->iface, NEF_STICKY);
        !           779:   if (!nb)
        !           780:   {
        !           781:     log(L_ERR "%s: Invalid remote address %I%J", p->p.name, n->addr, n->iface);
        !           782:     return;
        !           783:   }
        !           784: 
        !           785:   if (nb->data)
        !           786:   {
        !           787:     log(L_ERR "%s: Duplicate neighbor %I", p->p.name, n->addr);
        !           788:     return;
        !           789:   }
        !           790: 
        !           791:   n->neigh = nb;
        !           792:   nb->data = n;
        !           793: 
        !           794:   if (nb->scope > 0)
        !           795:     bfd_neigh_notify(nb);
        !           796:   else
        !           797:     TRACE(D_EVENTS, "Waiting for %I%J to become my neighbor", n->addr, n->iface);
        !           798: }
        !           799: 
        !           800: static void
        !           801: bfd_stop_neighbor(struct bfd_proto *p UNUSED, struct bfd_neighbor *n)
        !           802: {
        !           803:   if (n->neigh)
        !           804:     n->neigh->data = NULL;
        !           805:   n->neigh = NULL;
        !           806: 
        !           807:   rfree(n->req);
        !           808:   n->req = NULL;
        !           809: }
        !           810: 
        !           811: static inline int
        !           812: bfd_same_neighbor(struct bfd_neighbor *x, struct bfd_neighbor *y)
        !           813: {
        !           814:   return ipa_equal(x->addr, y->addr) && ipa_equal(x->local, y->local) &&
        !           815:     (x->iface == y->iface) && (x->multihop == y->multihop);
        !           816: }
        !           817: 
        !           818: static void
        !           819: bfd_reconfigure_neighbors(struct bfd_proto *p, struct bfd_config *new)
        !           820: {
        !           821:   struct bfd_config *old = (struct bfd_config *) (p->p.cf);
        !           822:   struct bfd_neighbor *on, *nn;
        !           823: 
        !           824:   WALK_LIST(on, old->neigh_list)
        !           825:   {
        !           826:     WALK_LIST(nn, new->neigh_list)
        !           827:       if (bfd_same_neighbor(nn, on))
        !           828:       {
        !           829:        nn->neigh = on->neigh;
        !           830:        if (nn->neigh)
        !           831:          nn->neigh->data = nn;
        !           832: 
        !           833:        nn->req = on->req;
        !           834:        nn->active = 1;
        !           835:        return;
        !           836:       }
        !           837: 
        !           838:     bfd_stop_neighbor(p, on);
        !           839:   }
        !           840: 
        !           841:   WALK_LIST(nn, new->neigh_list)
        !           842:     if (!nn->active)
        !           843:       bfd_start_neighbor(p, nn);
        !           844: }
        !           845: 
        !           846: 
        !           847: /*
        !           848:  *     BFD notify socket
        !           849:  */
        !           850: 
        !           851: /* This core notify code should be replaced after main loop transition to birdloop */
        !           852: 
        !           853: int pipe(int pipefd[2]);
        !           854: void pipe_drain(int fd);
        !           855: void pipe_kick(int fd);
        !           856: 
        !           857: static int
        !           858: bfd_notify_hook(sock *sk, uint len UNUSED)
        !           859: {
        !           860:   struct bfd_proto *p = sk->data;
        !           861:   struct bfd_session *s;
        !           862:   list tmp_list;
        !           863:   u8 state, diag;
        !           864:   node *n, *nn;
        !           865: 
        !           866:   pipe_drain(sk->fd);
        !           867: 
        !           868:   bfd_lock_sessions(p);
        !           869:   init_list(&tmp_list);
        !           870:   add_tail_list(&tmp_list, &p->notify_list);
        !           871:   init_list(&p->notify_list);
        !           872:   bfd_unlock_sessions(p);
        !           873: 
        !           874:   WALK_LIST_FIRST(s, tmp_list)
        !           875:   {
        !           876:     bfd_lock_sessions(p);
        !           877:     rem_node(&s->n);
        !           878:     state = s->loc_state;
        !           879:     diag = s->loc_diag;
        !           880:     bfd_unlock_sessions(p);
        !           881: 
        !           882:     /* FIXME: convert to btime and move to bfd_session_update_state() */
        !           883:     s->last_state_change = now;
        !           884: 
        !           885:     s->notify_running = 1;
        !           886:     WALK_LIST_DELSAFE(n, nn, s->request_list)
        !           887:       bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), state, diag);
        !           888:     s->notify_running = 0;
        !           889: 
        !           890:     /* Remove the session if all requests were removed in notify hooks */
        !           891:     if (EMPTY_LIST(s->request_list))
        !           892:       bfd_remove_session(p, s);
        !           893:   }
        !           894: 
        !           895:   return 0;
        !           896: }
        !           897: 
        !           898: static inline void
        !           899: bfd_notify_kick(struct bfd_proto *p)
        !           900: {
        !           901:   pipe_kick(p->notify_ws->fd);
        !           902: }
        !           903: 
        !           904: static void
        !           905: bfd_noterr_hook(sock *sk, int err)
        !           906: {
        !           907:   struct bfd_proto *p = sk->data;
        !           908:   log(L_ERR "%s: Notify socket error: %m", p->p.name, err);
        !           909: }
        !           910: 
        !           911: static void
        !           912: bfd_notify_init(struct bfd_proto *p)
        !           913: {
        !           914:   int pfds[2];
        !           915:   sock *sk;
        !           916: 
        !           917:   int rv = pipe(pfds);
        !           918:   if (rv < 0)
        !           919:     die("pipe: %m");
        !           920: 
        !           921:   sk = sk_new(p->p.pool);
        !           922:   sk->type = SK_MAGIC;
        !           923:   sk->rx_hook = bfd_notify_hook;
        !           924:   sk->err_hook = bfd_noterr_hook;
        !           925:   sk->fd = pfds[0];
        !           926:   sk->data = p;
        !           927:   if (sk_open(sk) < 0)
        !           928:     die("bfd: sk_open failed");
        !           929:   p->notify_rs = sk;
        !           930: 
        !           931:   /* The write sock is not added to any event loop */
        !           932:   sk = sk_new(p->p.pool);
        !           933:   sk->type = SK_MAGIC;
        !           934:   sk->fd = pfds[1];
        !           935:   sk->data = p;
        !           936:   sk->flags = SKF_THREAD;
        !           937:   if (sk_open(sk) < 0)
        !           938:     die("bfd: sk_open failed");
        !           939:   p->notify_ws = sk;
        !           940: }
        !           941: 
        !           942: 
        !           943: /*
        !           944:  *     BFD protocol glue
        !           945:  */
        !           946: 
        !           947: void
        !           948: bfd_init_all(void)
        !           949: {
        !           950:   init_list(&bfd_proto_list);
        !           951:   init_list(&bfd_wait_list);
        !           952: }
        !           953: 
        !           954: static struct proto *
        !           955: bfd_init(struct proto_config *c)
        !           956: {
        !           957:   struct proto *p = proto_new(c, sizeof(struct bfd_proto));
        !           958: 
        !           959:   p->neigh_notify = bfd_neigh_notify;
        !           960: 
        !           961:   return p;
        !           962: }
        !           963: 
        !           964: static int
        !           965: bfd_start(struct proto *P)
        !           966: {
        !           967:   struct bfd_proto *p = (struct bfd_proto *) P;
        !           968:   struct bfd_config *cf = (struct bfd_config *) (P->cf);
        !           969: 
        !           970:   p->loop = birdloop_new();
        !           971:   p->tpool = rp_new(NULL, "BFD thread root");
        !           972:   pthread_spin_init(&p->lock, PTHREAD_PROCESS_PRIVATE);
        !           973: 
        !           974:   p->session_slab = sl_new(P->pool, sizeof(struct bfd_session));
        !           975:   HASH_INIT(p->session_hash_id, P->pool, 8);
        !           976:   HASH_INIT(p->session_hash_ip, P->pool, 8);
        !           977: 
        !           978:   init_list(&p->iface_list);
        !           979: 
        !           980:   init_list(&p->notify_list);
        !           981:   bfd_notify_init(p);
        !           982: 
        !           983:   add_tail(&bfd_proto_list, &p->bfd_node);
        !           984: 
        !           985:   birdloop_enter(p->loop);
        !           986:   p->rx_1 = bfd_open_rx_sk(p, 0);
        !           987:   p->rx_m = bfd_open_rx_sk(p, 1);
        !           988:   birdloop_leave(p->loop);
        !           989: 
        !           990:   bfd_take_requests(p);
        !           991: 
        !           992:   struct bfd_neighbor *n;
        !           993:   WALK_LIST(n, cf->neigh_list)
        !           994:     bfd_start_neighbor(p, n);
        !           995: 
        !           996:   birdloop_start(p->loop);
        !           997: 
        !           998:   return PS_UP;
        !           999: }
        !          1000: 
        !          1001: 
        !          1002: static int
        !          1003: bfd_shutdown(struct proto *P)
        !          1004: {
        !          1005:   struct bfd_proto *p = (struct bfd_proto *) P;
        !          1006:   struct bfd_config *cf = (struct bfd_config *) (P->cf);
        !          1007: 
        !          1008:   rem_node(&p->bfd_node);
        !          1009: 
        !          1010:   birdloop_stop(p->loop);
        !          1011: 
        !          1012:   struct bfd_neighbor *n;
        !          1013:   WALK_LIST(n, cf->neigh_list)
        !          1014:     bfd_stop_neighbor(p, n);
        !          1015: 
        !          1016:   bfd_drop_requests(p);
        !          1017: 
        !          1018:   /* FIXME: This is hack */
        !          1019:   birdloop_enter(p->loop);
        !          1020:   rfree(p->tpool);
        !          1021:   birdloop_leave(p->loop);
        !          1022: 
        !          1023:   birdloop_free(p->loop);
        !          1024: 
        !          1025:   return PS_DOWN;
        !          1026: }
        !          1027: 
        !          1028: static int
        !          1029: bfd_reconfigure(struct proto *P, struct proto_config *c)
        !          1030: {
        !          1031:   struct bfd_proto *p = (struct bfd_proto *) P;
        !          1032:   // struct bfd_config *old = (struct bfd_config *) (P->cf);
        !          1033:   struct bfd_config *new = (struct bfd_config *) c;
        !          1034:   struct bfd_iface *ifa;
        !          1035: 
        !          1036:   birdloop_mask_wakeups(p->loop);
        !          1037: 
        !          1038:   WALK_LIST(ifa, p->iface_list)
        !          1039:     bfd_reconfigure_iface(p, ifa, new);
        !          1040: 
        !          1041:   HASH_WALK(p->session_hash_id, next_id, s)
        !          1042:   {
        !          1043:     if (s->ifa->changed)
        !          1044:       bfd_reconfigure_session(p, s);
        !          1045:   }
        !          1046:   HASH_WALK_END;
        !          1047: 
        !          1048:   bfd_reconfigure_neighbors(p, new);
        !          1049: 
        !          1050:   birdloop_unmask_wakeups(p->loop);
        !          1051: 
        !          1052:   return 1;
        !          1053: }
        !          1054: 
        !          1055: /* Ensure one instance */
        !          1056: struct bfd_config *bfd_cf;
        !          1057: 
        !          1058: static void
        !          1059: bfd_preconfig(struct protocol *P UNUSED, struct config *c UNUSED)
        !          1060: {
        !          1061:   bfd_cf = NULL;
        !          1062: }
        !          1063: 
        !          1064: static void
        !          1065: bfd_copy_config(struct proto_config *dest, struct proto_config *src UNUSED)
        !          1066: {
        !          1067:   struct bfd_config *d = (struct bfd_config *) dest;
        !          1068:   // struct bfd_config *s = (struct bfd_config *) src;
        !          1069: 
        !          1070:   /* We clean up patt_list and neigh_list, neighbors and ifaces are non-sharable */
        !          1071:   init_list(&d->patt_list);
        !          1072:   init_list(&d->neigh_list);
        !          1073: }
        !          1074: 
        !          1075: void
        !          1076: bfd_show_sessions(struct proto *P)
        !          1077: {
        !          1078:   byte tbuf[TM_DATETIME_BUFFER_SIZE];
        !          1079:   struct bfd_proto *p = (struct bfd_proto *) P;
        !          1080:   uint state, diag UNUSED;
        !          1081:   u32 tx_int, timeout;
        !          1082:   const char *ifname;
        !          1083: 
        !          1084:   if (p->p.proto_state != PS_UP)
        !          1085:   {
        !          1086:     cli_msg(-1020, "%s: is not up", p->p.name);
        !          1087:     cli_msg(0, "");
        !          1088:     return;
        !          1089:   }
        !          1090: 
        !          1091:   cli_msg(-1020, "%s:", p->p.name);
        !          1092:   cli_msg(-1020, "%-25s %-10s %-10s %-10s  %8s %8s",
        !          1093:          "IP address", "Interface", "State", "Since", "Interval", "Timeout");
        !          1094: 
        !          1095: 
        !          1096:   HASH_WALK(p->session_hash_id, next_id, s)
        !          1097:   {
        !          1098:     /* FIXME: this is thread-unsafe, but perhaps harmless */
        !          1099:     state = s->loc_state;
        !          1100:     diag = s->loc_diag;
        !          1101:     ifname = (s->ifa && s->ifa->iface) ? s->ifa->iface->name : "---";
        !          1102:     tx_int = s->last_tx ? (MAX(s->des_min_tx_int, s->rem_min_rx_int) TO_MS) : 0;
        !          1103:     timeout = (MAX(s->req_min_rx_int, s->rem_min_tx_int) TO_MS) * s->rem_detect_mult;
        !          1104: 
        !          1105:     state = (state < 4) ? state : 0;
        !          1106:     tm_format_datetime(tbuf, &config->tf_proto, s->last_state_change);
        !          1107: 
        !          1108:     cli_msg(-1020, "%-25I %-10s %-10s %-10s  %3u.%03u  %3u.%03u",
        !          1109:            s->addr, ifname, bfd_state_names[state], tbuf,
        !          1110:            tx_int / 1000, tx_int % 1000, timeout / 1000, timeout % 1000);
        !          1111:   }
        !          1112:   HASH_WALK_END;
        !          1113: 
        !          1114:   cli_msg(0, "");
        !          1115: }
        !          1116: 
        !          1117: 
        !          1118: struct protocol proto_bfd = {
        !          1119:   .name =              "BFD",
        !          1120:   .template =          "bfd%d",
        !          1121:   .config_size =       sizeof(struct bfd_config),
        !          1122:   .init =              bfd_init,
        !          1123:   .start =             bfd_start,
        !          1124:   .shutdown =          bfd_shutdown,
        !          1125:   .reconfigure =       bfd_reconfigure,
        !          1126:   .preconfig =                 bfd_preconfig,
        !          1127:   .copy_config =       bfd_copy_config,
        !          1128: };

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