Annotation of embedaddon/bird2/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. A birdloop is
        !            68:  * associated with a thread (field @thread) in which event hooks are executed.
        !            69:  * Most functions for setting event sources (like sk_start() or tm_start()) must
        !            70:  * be called from the context of that thread. Birdloop allows to temporarily
        !            71:  * acquire the context of that thread for the main thread by calling
        !            72:  * birdloop_enter() and then birdloop_leave(), which also ensures mutual
        !            73:  * exclusion with all event hooks. Note that resources associated with a
        !            74:  * birdloop (like timers) should be attached to the independent resource pool,
        !            75:  * detached from the main resource tree.
        !            76:  *
        !            77:  * There are two kinds of interaction between the BFD core (running in the BFD
        !            78:  * thread) and the rest of BFD (running in the main thread). The first kind are
        !            79:  * configuration calls from main thread to the BFD thread (like bfd_add_session()).
        !            80:  * These calls are synchronous and use birdloop_enter() mechanism for mutual
        !            81:  * exclusion. The second kind is a notification about session changes from the
        !            82:  * BFD thread to the main thread. This is done in an asynchronous way, sesions
        !            83:  * with pending notifications are linked (in the BFD thread) to @notify_list in
        !            84:  * &bfd_proto, and then bfd_notify_hook() in the main thread is activated using
        !            85:  * bfd_notify_kick() and a pipe. The hook then processes scheduled sessions and
        !            86:  * calls hooks from associated BFD requests. This @notify_list (and state fields
        !            87:  * in structure &bfd_session) is protected by a spinlock in &bfd_proto and
        !            88:  * functions bfd_lock_sessions() / bfd_unlock_sessions().
        !            89:  *
        !            90:  * There are few data races (accessing @p->p.debug from TRACE() from the BFD
        !            91:  * thread and accessing some some private fields of %bfd_session from
        !            92:  * bfd_show_sessions() from the main thread, but these are harmless (i hope).
        !            93:  *
        !            94:  * TODO: document functions and access restrictions for fields in BFD structures.
        !            95:  *
        !            96:  * Supported standards:
        !            97:  * - RFC 5880 - main BFD standard
        !            98:  * - RFC 5881 - BFD for IP links
        !            99:  * - RFC 5882 - generic application of BFD
        !           100:  * - RFC 5883 - BFD for multihop paths
        !           101:  */
        !           102: 
        !           103: #include "bfd.h"
        !           104: 
        !           105: 
        !           106: #define HASH_ID_KEY(n)         n->loc_id
        !           107: #define HASH_ID_NEXT(n)                n->next_id
        !           108: #define HASH_ID_EQ(a,b)                a == b
        !           109: #define HASH_ID_FN(k)          k
        !           110: 
        !           111: #define HASH_IP_KEY(n)         n->addr
        !           112: #define HASH_IP_NEXT(n)                n->next_ip
        !           113: #define HASH_IP_EQ(a,b)                ipa_equal(a,b)
        !           114: #define HASH_IP_FN(k)          ipa_hash(k)
        !           115: 
        !           116: static list bfd_proto_list;
        !           117: static list bfd_wait_list;
        !           118: 
        !           119: const char *bfd_state_names[] = { "AdminDown", "Down", "Init", "Up" };
        !           120: 
        !           121: static void bfd_session_set_min_tx(struct bfd_session *s, u32 val);
        !           122: static struct bfd_iface *bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface);
        !           123: static void bfd_free_iface(struct bfd_iface *ifa);
        !           124: static inline void bfd_notify_kick(struct bfd_proto *p);
        !           125: 
        !           126: 
        !           127: /*
        !           128:  *     BFD sessions
        !           129:  */
        !           130: 
        !           131: static void
        !           132: bfd_session_update_state(struct bfd_session *s, uint state, uint diag)
        !           133: {
        !           134:   struct bfd_proto *p = s->ifa->bfd;
        !           135:   uint old_state = s->loc_state;
        !           136:   int notify;
        !           137: 
        !           138:   if (state == old_state)
        !           139:     return;
        !           140: 
        !           141:   TRACE(D_EVENTS, "Session to %I changed state from %s to %s",
        !           142:        s->addr, bfd_state_names[old_state], bfd_state_names[state]);
        !           143: 
        !           144:   bfd_lock_sessions(p);
        !           145:   s->loc_state = state;
        !           146:   s->loc_diag = diag;
        !           147:   s->last_state_change = current_time();
        !           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:   tm_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:   tm_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 || !tm_active(s->tx_timer))
        !           215:   {
        !           216:     s->last_tx = 0;
        !           217:     tm_start(s->tx_timer, 0);
        !           218:   }
        !           219: 
        !           220:   return;
        !           221: 
        !           222:  stop:
        !           223:   tm_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(timer *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(timer *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 = tm_new_init(p->tpool, bfd_tx_timer_hook, s, 0, 0);
        !           436:   s->hold_timer = tm_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 = current_time();
        !           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:   if (p->p.vrf_set && (p->p.vrf != req->vrf))
        !           628:     return 0;
        !           629: 
        !           630:   struct bfd_session *s = bfd_find_session_by_addr(p, req->addr);
        !           631:   u8 state, diag;
        !           632: 
        !           633:   if (!s)
        !           634:     s = bfd_add_session(p, req->addr, req->local, req->iface);
        !           635: 
        !           636:   rem_node(&req->n);
        !           637:   add_tail(&s->request_list, &req->n);
        !           638:   req->session = s;
        !           639: 
        !           640:   bfd_lock_sessions(p);
        !           641:   state = s->loc_state;
        !           642:   diag = s->loc_diag;
        !           643:   bfd_unlock_sessions(p);
        !           644: 
        !           645:   bfd_request_notify(req, state, diag);
        !           646: 
        !           647:   return 1;
        !           648: }
        !           649: 
        !           650: static void
        !           651: bfd_submit_request(struct bfd_request *req)
        !           652: {
        !           653:   node *n;
        !           654: 
        !           655:   WALK_LIST(n, bfd_proto_list)
        !           656:     if (bfd_add_request(SKIP_BACK(struct bfd_proto, bfd_node, n), req))
        !           657:       return;
        !           658: 
        !           659:   rem_node(&req->n);
        !           660:   add_tail(&bfd_wait_list, &req->n);
        !           661:   req->session = NULL;
        !           662:   bfd_request_notify(req, BFD_STATE_ADMIN_DOWN, 0);
        !           663: }
        !           664: 
        !           665: static void
        !           666: bfd_take_requests(struct bfd_proto *p)
        !           667: {
        !           668:   node *n, *nn;
        !           669: 
        !           670:   WALK_LIST_DELSAFE(n, nn, bfd_wait_list)
        !           671:     bfd_add_request(p, SKIP_BACK(struct bfd_request, n, n));
        !           672: }
        !           673: 
        !           674: static void
        !           675: bfd_drop_requests(struct bfd_proto *p)
        !           676: {
        !           677:   node *n;
        !           678: 
        !           679:   HASH_WALK(p->session_hash_id, next_id, s)
        !           680:   {
        !           681:     /* We assume that p is not in bfd_proto_list */
        !           682:     WALK_LIST_FIRST(n, s->request_list)
        !           683:       bfd_submit_request(SKIP_BACK(struct bfd_request, n, n));
        !           684:   }
        !           685:   HASH_WALK_END;
        !           686: }
        !           687: 
        !           688: static struct resclass bfd_request_class;
        !           689: 
        !           690: struct bfd_request *
        !           691: bfd_request_session(pool *p, ip_addr addr, ip_addr local,
        !           692:                    struct iface *iface, struct iface *vrf,
        !           693:                    void (*hook)(struct bfd_request *), void *data)
        !           694: {
        !           695:   struct bfd_request *req = ralloc(p, &bfd_request_class);
        !           696: 
        !           697:   /* Hack: self-link req->n, we will call rem_node() on it */
        !           698:   req->n.prev = req->n.next = &req->n;
        !           699: 
        !           700:   req->addr = addr;
        !           701:   req->local = local;
        !           702:   req->iface = iface;
        !           703:   req->vrf = vrf;
        !           704: 
        !           705:   bfd_submit_request(req);
        !           706: 
        !           707:   req->hook = hook;
        !           708:   req->data = data;
        !           709: 
        !           710:   return req;
        !           711: }
        !           712: 
        !           713: static void
        !           714: bfd_request_free(resource *r)
        !           715: {
        !           716:   struct bfd_request *req = (struct bfd_request *) r;
        !           717:   struct bfd_session *s = req->session;
        !           718: 
        !           719:   rem_node(&req->n);
        !           720: 
        !           721:   /* Remove the session if there is no request for it. Skip that if
        !           722:      inside notify hooks, will be handled by bfd_notify_hook() itself */
        !           723: 
        !           724:   if (s && EMPTY_LIST(s->request_list) && !s->notify_running)
        !           725:     bfd_remove_session(s->ifa->bfd, s);
        !           726: }
        !           727: 
        !           728: static void
        !           729: bfd_request_dump(resource *r)
        !           730: {
        !           731:   struct bfd_request *req = (struct bfd_request *) r;
        !           732: 
        !           733:   debug("(code %p, data %p)\n", req->hook, req->data);
        !           734: }
        !           735: 
        !           736: static struct resclass bfd_request_class = {
        !           737:   "BFD request",
        !           738:   sizeof(struct bfd_request),
        !           739:   bfd_request_free,
        !           740:   bfd_request_dump,
        !           741:   NULL,
        !           742:   NULL
        !           743: };
        !           744: 
        !           745: 
        !           746: /*
        !           747:  *     BFD neighbors
        !           748:  */
        !           749: 
        !           750: static void
        !           751: bfd_neigh_notify(struct neighbor *nb)
        !           752: {
        !           753:   struct bfd_proto *p = (struct bfd_proto *) nb->proto;
        !           754:   struct bfd_neighbor *n = nb->data;
        !           755: 
        !           756:   if (!n)
        !           757:     return;
        !           758: 
        !           759:   if ((nb->scope > 0) && !n->req)
        !           760:   {
        !           761:     ip_addr local = ipa_nonzero(n->local) ? n->local : nb->ifa->ip;
        !           762:     n->req = bfd_request_session(p->p.pool, n->addr, local, nb->iface, p->p.vrf, NULL, NULL);
        !           763:   }
        !           764: 
        !           765:   if ((nb->scope <= 0) && n->req)
        !           766:   {
        !           767:     rfree(n->req);
        !           768:     n->req = NULL;
        !           769:   }
        !           770: }
        !           771: 
        !           772: static void
        !           773: bfd_start_neighbor(struct bfd_proto *p, struct bfd_neighbor *n)
        !           774: {
        !           775:   n->active = 1;
        !           776: 
        !           777:   if (n->multihop)
        !           778:   {
        !           779:     n->req = bfd_request_session(p->p.pool, n->addr, n->local, NULL, p->p.vrf, NULL, NULL);
        !           780:     return;
        !           781:   }
        !           782: 
        !           783:   struct neighbor *nb = neigh_find(&p->p, n->addr, n->iface, NEF_STICKY);
        !           784:   if (!nb)
        !           785:   {
        !           786:     log(L_ERR "%s: Invalid remote address %I%J", p->p.name, n->addr, n->iface);
        !           787:     return;
        !           788:   }
        !           789: 
        !           790:   if (nb->data)
        !           791:   {
        !           792:     log(L_ERR "%s: Duplicate neighbor %I", p->p.name, n->addr);
        !           793:     return;
        !           794:   }
        !           795: 
        !           796:   n->neigh = nb;
        !           797:   nb->data = n;
        !           798: 
        !           799:   if (nb->scope > 0)
        !           800:     bfd_neigh_notify(nb);
        !           801:   else
        !           802:     TRACE(D_EVENTS, "Waiting for %I%J to become my neighbor", n->addr, n->iface);
        !           803: }
        !           804: 
        !           805: static void
        !           806: bfd_stop_neighbor(struct bfd_proto *p UNUSED, struct bfd_neighbor *n)
        !           807: {
        !           808:   if (n->neigh)
        !           809:     n->neigh->data = NULL;
        !           810:   n->neigh = NULL;
        !           811: 
        !           812:   rfree(n->req);
        !           813:   n->req = NULL;
        !           814: }
        !           815: 
        !           816: static inline int
        !           817: bfd_same_neighbor(struct bfd_neighbor *x, struct bfd_neighbor *y)
        !           818: {
        !           819:   return ipa_equal(x->addr, y->addr) && ipa_equal(x->local, y->local) &&
        !           820:     (x->iface == y->iface) && (x->multihop == y->multihop);
        !           821: }
        !           822: 
        !           823: static void
        !           824: bfd_reconfigure_neighbors(struct bfd_proto *p, struct bfd_config *new)
        !           825: {
        !           826:   struct bfd_config *old = (struct bfd_config *) (p->p.cf);
        !           827:   struct bfd_neighbor *on, *nn;
        !           828: 
        !           829:   WALK_LIST(on, old->neigh_list)
        !           830:   {
        !           831:     WALK_LIST(nn, new->neigh_list)
        !           832:       if (bfd_same_neighbor(nn, on))
        !           833:       {
        !           834:        nn->neigh = on->neigh;
        !           835:        if (nn->neigh)
        !           836:          nn->neigh->data = nn;
        !           837: 
        !           838:        nn->req = on->req;
        !           839:        nn->active = 1;
        !           840:        goto next;
        !           841:       }
        !           842: 
        !           843:     bfd_stop_neighbor(p, on);
        !           844:   next:;
        !           845:   }
        !           846: 
        !           847:   WALK_LIST(nn, new->neigh_list)
        !           848:     if (!nn->active)
        !           849:       bfd_start_neighbor(p, nn);
        !           850: }
        !           851: 
        !           852: 
        !           853: /*
        !           854:  *     BFD notify socket
        !           855:  */
        !           856: 
        !           857: /* This core notify code should be replaced after main loop transition to birdloop */
        !           858: 
        !           859: int pipe(int pipefd[2]);
        !           860: void pipe_drain(int fd);
        !           861: void pipe_kick(int fd);
        !           862: 
        !           863: static int
        !           864: bfd_notify_hook(sock *sk, uint len UNUSED)
        !           865: {
        !           866:   struct bfd_proto *p = sk->data;
        !           867:   struct bfd_session *s;
        !           868:   list tmp_list;
        !           869:   u8 state, diag;
        !           870:   node *n, *nn;
        !           871: 
        !           872:   pipe_drain(sk->fd);
        !           873: 
        !           874:   bfd_lock_sessions(p);
        !           875:   init_list(&tmp_list);
        !           876:   add_tail_list(&tmp_list, &p->notify_list);
        !           877:   init_list(&p->notify_list);
        !           878:   bfd_unlock_sessions(p);
        !           879: 
        !           880:   WALK_LIST_FIRST(s, tmp_list)
        !           881:   {
        !           882:     bfd_lock_sessions(p);
        !           883:     rem_node(&s->n);
        !           884:     state = s->loc_state;
        !           885:     diag = s->loc_diag;
        !           886:     bfd_unlock_sessions(p);
        !           887: 
        !           888:     s->notify_running = 1;
        !           889:     WALK_LIST_DELSAFE(n, nn, s->request_list)
        !           890:       bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), state, diag);
        !           891:     s->notify_running = 0;
        !           892: 
        !           893:     /* Remove the session if all requests were removed in notify hooks */
        !           894:     if (EMPTY_LIST(s->request_list))
        !           895:       bfd_remove_session(p, s);
        !           896:   }
        !           897: 
        !           898:   return 0;
        !           899: }
        !           900: 
        !           901: static inline void
        !           902: bfd_notify_kick(struct bfd_proto *p)
        !           903: {
        !           904:   pipe_kick(p->notify_ws->fd);
        !           905: }
        !           906: 
        !           907: static void
        !           908: bfd_noterr_hook(sock *sk, int err)
        !           909: {
        !           910:   struct bfd_proto *p = sk->data;
        !           911:   log(L_ERR "%s: Notify socket error: %m", p->p.name, err);
        !           912: }
        !           913: 
        !           914: static void
        !           915: bfd_notify_init(struct bfd_proto *p)
        !           916: {
        !           917:   int pfds[2];
        !           918:   sock *sk;
        !           919: 
        !           920:   int rv = pipe(pfds);
        !           921:   if (rv < 0)
        !           922:     die("pipe: %m");
        !           923: 
        !           924:   sk = sk_new(p->p.pool);
        !           925:   sk->type = SK_MAGIC;
        !           926:   sk->rx_hook = bfd_notify_hook;
        !           927:   sk->err_hook = bfd_noterr_hook;
        !           928:   sk->fd = pfds[0];
        !           929:   sk->data = p;
        !           930:   if (sk_open(sk) < 0)
        !           931:     die("bfd: sk_open failed");
        !           932:   p->notify_rs = sk;
        !           933: 
        !           934:   /* The write sock is not added to any event loop */
        !           935:   sk = sk_new(p->p.pool);
        !           936:   sk->type = SK_MAGIC;
        !           937:   sk->fd = pfds[1];
        !           938:   sk->data = p;
        !           939:   sk->flags = SKF_THREAD;
        !           940:   if (sk_open(sk) < 0)
        !           941:     die("bfd: sk_open failed");
        !           942:   p->notify_ws = sk;
        !           943: }
        !           944: 
        !           945: 
        !           946: /*
        !           947:  *     BFD protocol glue
        !           948:  */
        !           949: 
        !           950: void
        !           951: bfd_init_all(void)
        !           952: {
        !           953:   init_list(&bfd_proto_list);
        !           954:   init_list(&bfd_wait_list);
        !           955: }
        !           956: 
        !           957: static struct proto *
        !           958: bfd_init(struct proto_config *c)
        !           959: {
        !           960:   struct proto *p = proto_new(c);
        !           961: 
        !           962:   p->neigh_notify = bfd_neigh_notify;
        !           963: 
        !           964:   return p;
        !           965: }
        !           966: 
        !           967: static int
        !           968: bfd_start(struct proto *P)
        !           969: {
        !           970:   struct bfd_proto *p = (struct bfd_proto *) P;
        !           971:   struct bfd_config *cf = (struct bfd_config *) (P->cf);
        !           972: 
        !           973:   p->loop = birdloop_new();
        !           974:   p->tpool = rp_new(NULL, "BFD thread root");
        !           975:   pthread_spin_init(&p->lock, PTHREAD_PROCESS_PRIVATE);
        !           976: 
        !           977:   p->session_slab = sl_new(P->pool, sizeof(struct bfd_session));
        !           978:   HASH_INIT(p->session_hash_id, P->pool, 8);
        !           979:   HASH_INIT(p->session_hash_ip, P->pool, 8);
        !           980: 
        !           981:   init_list(&p->iface_list);
        !           982: 
        !           983:   init_list(&p->notify_list);
        !           984:   bfd_notify_init(p);
        !           985: 
        !           986:   add_tail(&bfd_proto_list, &p->bfd_node);
        !           987: 
        !           988:   birdloop_enter(p->loop);
        !           989:   p->rx4_1 = bfd_open_rx_sk(p, 0, SK_IPV4);
        !           990:   p->rx4_m = bfd_open_rx_sk(p, 1, SK_IPV4);
        !           991:   p->rx6_1 = bfd_open_rx_sk(p, 0, SK_IPV6);
        !           992:   p->rx6_m = bfd_open_rx_sk(p, 1, SK_IPV6);
        !           993:   birdloop_leave(p->loop);
        !           994: 
        !           995:   bfd_take_requests(p);
        !           996: 
        !           997:   struct bfd_neighbor *n;
        !           998:   WALK_LIST(n, cf->neigh_list)
        !           999:     bfd_start_neighbor(p, n);
        !          1000: 
        !          1001:   birdloop_start(p->loop);
        !          1002: 
        !          1003:   return PS_UP;
        !          1004: }
        !          1005: 
        !          1006: 
        !          1007: static int
        !          1008: bfd_shutdown(struct proto *P)
        !          1009: {
        !          1010:   struct bfd_proto *p = (struct bfd_proto *) P;
        !          1011:   struct bfd_config *cf = (struct bfd_config *) (P->cf);
        !          1012: 
        !          1013:   rem_node(&p->bfd_node);
        !          1014: 
        !          1015:   birdloop_stop(p->loop);
        !          1016: 
        !          1017:   struct bfd_neighbor *n;
        !          1018:   WALK_LIST(n, cf->neigh_list)
        !          1019:     bfd_stop_neighbor(p, n);
        !          1020: 
        !          1021:   bfd_drop_requests(p);
        !          1022: 
        !          1023:   /* FIXME: This is hack */
        !          1024:   birdloop_enter(p->loop);
        !          1025:   rfree(p->tpool);
        !          1026:   birdloop_leave(p->loop);
        !          1027: 
        !          1028:   birdloop_free(p->loop);
        !          1029: 
        !          1030:   return PS_DOWN;
        !          1031: }
        !          1032: 
        !          1033: static int
        !          1034: bfd_reconfigure(struct proto *P, struct proto_config *c)
        !          1035: {
        !          1036:   struct bfd_proto *p = (struct bfd_proto *) P;
        !          1037:   // struct bfd_config *old = (struct bfd_config *) (P->cf);
        !          1038:   struct bfd_config *new = (struct bfd_config *) c;
        !          1039:   struct bfd_iface *ifa;
        !          1040: 
        !          1041:   birdloop_mask_wakeups(p->loop);
        !          1042: 
        !          1043:   WALK_LIST(ifa, p->iface_list)
        !          1044:     bfd_reconfigure_iface(p, ifa, new);
        !          1045: 
        !          1046:   HASH_WALK(p->session_hash_id, next_id, s)
        !          1047:   {
        !          1048:     if (s->ifa->changed)
        !          1049:       bfd_reconfigure_session(p, s);
        !          1050:   }
        !          1051:   HASH_WALK_END;
        !          1052: 
        !          1053:   bfd_reconfigure_neighbors(p, new);
        !          1054: 
        !          1055:   birdloop_unmask_wakeups(p->loop);
        !          1056: 
        !          1057:   return 1;
        !          1058: }
        !          1059: 
        !          1060: static void
        !          1061: bfd_copy_config(struct proto_config *dest, struct proto_config *src UNUSED)
        !          1062: {
        !          1063:   struct bfd_config *d = (struct bfd_config *) dest;
        !          1064:   // struct bfd_config *s = (struct bfd_config *) src;
        !          1065: 
        !          1066:   /* We clean up patt_list and neigh_list, neighbors and ifaces are non-sharable */
        !          1067:   init_list(&d->patt_list);
        !          1068:   init_list(&d->neigh_list);
        !          1069: }
        !          1070: 
        !          1071: void
        !          1072: bfd_show_sessions(struct proto *P)
        !          1073: {
        !          1074:   byte tbuf[TM_DATETIME_BUFFER_SIZE];
        !          1075:   struct bfd_proto *p = (struct bfd_proto *) P;
        !          1076:   uint state, diag UNUSED;
        !          1077:   btime tx_int, timeout;
        !          1078:   const char *ifname;
        !          1079: 
        !          1080:   if (p->p.proto_state != PS_UP)
        !          1081:   {
        !          1082:     cli_msg(-1020, "%s: is not up", p->p.name);
        !          1083:     cli_msg(0, "");
        !          1084:     return;
        !          1085:   }
        !          1086: 
        !          1087:   cli_msg(-1020, "%s:", p->p.name);
        !          1088:   cli_msg(-1020, "%-25s %-10s %-10s %-12s  %8s %8s",
        !          1089:          "IP address", "Interface", "State", "Since", "Interval", "Timeout");
        !          1090: 
        !          1091: 
        !          1092:   HASH_WALK(p->session_hash_id, next_id, s)
        !          1093:   {
        !          1094:     /* FIXME: this is thread-unsafe, but perhaps harmless */
        !          1095:     state = s->loc_state;
        !          1096:     diag = s->loc_diag;
        !          1097:     ifname = (s->ifa && s->ifa->iface) ? s->ifa->iface->name : "---";
        !          1098:     tx_int = s->last_tx ? MAX(s->des_min_tx_int, s->rem_min_rx_int) : 0;
        !          1099:     timeout = (btime) MAX(s->req_min_rx_int, s->rem_min_tx_int) * s->rem_detect_mult;
        !          1100: 
        !          1101:     state = (state < 4) ? state : 0;
        !          1102:     tm_format_time(tbuf, &config->tf_proto, s->last_state_change);
        !          1103: 
        !          1104:     cli_msg(-1020, "%-25I %-10s %-10s %-12s  %7t  %7t",
        !          1105:            s->addr, ifname, bfd_state_names[state], tbuf, tx_int, timeout);
        !          1106:   }
        !          1107:   HASH_WALK_END;
        !          1108: 
        !          1109:   cli_msg(0, "");
        !          1110: }
        !          1111: 
        !          1112: 
        !          1113: struct protocol proto_bfd = {
        !          1114:   .name =              "BFD",
        !          1115:   .template =          "bfd%d",
        !          1116:   .class =             PROTOCOL_BFD,
        !          1117:   .proto_size =                sizeof(struct bfd_proto),
        !          1118:   .config_size =       sizeof(struct bfd_config),
        !          1119:   .init =              bfd_init,
        !          1120:   .start =             bfd_start,
        !          1121:   .shutdown =          bfd_shutdown,
        !          1122:   .reconfigure =       bfd_reconfigure,
        !          1123:   .copy_config =       bfd_copy_config,
        !          1124: };

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