/* * BIRD -- Bidirectional Forwarding Detection (BFD) * * Can be freely distributed and used under the terms of the GNU GPL. */ /** * DOC: Bidirectional Forwarding Detection * * The BFD protocol is implemented in three files: |bfd.c| containing the * protocol logic and the protocol glue with BIRD core, |packets.c| handling BFD * packet processing, RX, TX and protocol sockets. |io.c| then contains generic * code for the event loop, threads and event sources (sockets, microsecond * timers). This generic code will be merged to the main BIRD I/O code in the * future. * * The BFD implementation uses a separate thread with an internal event loop for * handling the protocol logic, which requires high-res and low-latency timing, * so it is not affected by the rest of BIRD, which has several low-granularity * hooks in the main loop, uses second-based timers and cannot offer good * latency. The core of BFD protocol (the code related to BFD sessions, * interfaces and packets) runs in the BFD thread, while the rest (the code * related to BFD requests, BFD neighbors and the protocol glue) runs in the * main thread. * * BFD sessions are represented by structure &bfd_session that contains a state * related to the session and two timers (TX timer for periodic packets and hold * timer for session timeout). These sessions are allocated from @session_slab * and are accessible by two hash tables, @session_hash_id (by session ID) and * @session_hash_ip (by IP addresses of neighbors and associated interfaces). * Slab and both hashes are in the main protocol structure &bfd_proto. The * protocol logic related to BFD sessions is implemented in internal functions * bfd_session_*(), which are expected to be called from the context of BFD * thread, and external functions bfd_add_session(), bfd_remove_session() and * bfd_reconfigure_session(), which form an interface to the BFD core for the * rest and are expected to be called from the context of main thread. * * Each BFD session has an associated BFD interface, represented by structure * &bfd_iface. A BFD interface contains a socket used for TX (the one for RX is * shared in &bfd_proto), an interface configuration and reference counter. * Compared to interface structures of other protocols, these structures are not * created and removed based on interface notification events, but according to * the needs of BFD sessions. When a new session is created, it requests a * proper BFD interface by function bfd_get_iface(), which either finds an * existing one in &iface_list (from &bfd_proto) or allocates a new one. When a * session is removed, an associated iface is discharged by bfd_free_iface(). * * BFD requests are the external API for the other protocols. When a protocol * wants a BFD session, it calls bfd_request_session(), which creates a * structure &bfd_request containing approprite information and an notify hook. * This structure is a resource associated with the caller's resource pool. When * a BFD protocol is available, a BFD request is submitted to the protocol, an * appropriate BFD session is found or created and the request is attached to * the session. When a session changes state, all attached requests (and related * protocols) are notified. Note that BFD requests do not depend on BFD protocol * running. When the BFD protocol is stopped or removed (or not available from * beginning), related BFD requests are stored in @bfd_wait_list, where waits * for a new protocol. * * BFD neighbors are just a way to statically configure BFD sessions without * requests from other protocol. Structures &bfd_neighbor are part of BFD * configuration (like static routes in the static protocol). BFD neighbors are * handled by BFD protocol like it is a BFD client -- when a BFD neighbor is * ready, the protocol just creates a BFD request like any other protocol. * * The protocol uses a new generic event loop (structure &birdloop) from |io.c|, * which supports sockets, timers and events like the main loop. A birdloop is * associated with a thread (field @thread) in which event hooks are executed. * Most functions for setting event sources (like sk_start() or tm_start()) must * be called from the context of that thread. Birdloop allows to temporarily * acquire the context of that thread for the main thread by calling * birdloop_enter() and then birdloop_leave(), which also ensures mutual * exclusion with all event hooks. Note that resources associated with a * birdloop (like timers) should be attached to the independent resource pool, * detached from the main resource tree. * * There are two kinds of interaction between the BFD core (running in the BFD * thread) and the rest of BFD (running in the main thread). The first kind are * configuration calls from main thread to the BFD thread (like bfd_add_session()). * These calls are synchronous and use birdloop_enter() mechanism for mutual * exclusion. The second kind is a notification about session changes from the * BFD thread to the main thread. This is done in an asynchronous way, sesions * with pending notifications are linked (in the BFD thread) to @notify_list in * &bfd_proto, and then bfd_notify_hook() in the main thread is activated using * a standard event sending code. The hook then processes scheduled sessions and * calls hooks from associated BFD requests. This @notify_list (and state fields * in structure &bfd_session) is protected by a spinlock in &bfd_proto and * functions bfd_lock_sessions() / bfd_unlock_sessions(). * * There are few data races (accessing @p->p.debug from TRACE() from the BFD * thread and accessing some some private fields of %bfd_session from * bfd_show_sessions() from the main thread, but these are harmless (i hope). * * TODO: document functions and access restrictions for fields in BFD structures. * * Supported standards: * - RFC 5880 - main BFD standard * - RFC 5881 - BFD for IP links * - RFC 5882 - generic application of BFD * - RFC 5883 - BFD for multihop paths */ #include "bfd.h" #define HASH_ID_KEY(n) n->loc_id #define HASH_ID_NEXT(n) n->next_id #define HASH_ID_EQ(a,b) a == b #define HASH_ID_FN(k) k #define HASH_IP_KEY(n) n->addr, n->ifindex #define HASH_IP_NEXT(n) n->next_ip #define HASH_IP_EQ(a1,n1,a2,n2) ipa_equal(a1, a2) && n1 == n2 #define HASH_IP_FN(a,n) ipa_hash(a) ^ u32_hash(n) #define BFD_LOCK LOCK_DOMAIN(rtable, bfd_global.lock) #define BFD_UNLOCK UNLOCK_DOMAIN(rtable, bfd_global.lock) static struct { DOMAIN(rtable) lock; list wait_list; list pickup_list; list proto_list; uint pickup_reload; } bfd_global; const char *bfd_state_names[] = { "AdminDown", "Down", "Init", "Up" }; static void bfd_session_set_min_tx(struct bfd_session *s, u32 val); static struct bfd_iface *bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface); static void bfd_free_iface(struct bfd_iface *ifa); /* * BFD sessions */ static inline struct bfd_session_config bfd_merge_options(const struct bfd_iface_config *cf, const struct bfd_options *opts) { return (struct bfd_session_config) { .min_rx_int = opts->min_rx_int ?: cf->min_rx_int, .min_tx_int = opts->min_tx_int ?: cf->min_tx_int, .idle_tx_int = opts->idle_tx_int ?: cf->idle_tx_int, .multiplier = opts->multiplier ?: cf->multiplier, .passive = opts->passive_set ? opts->passive : cf->passive }; } static void bfd_session_update_state(struct bfd_session *s, uint state, uint diag) { struct bfd_proto *p = s->ifa->bfd; uint old_state = s->loc_state; int notify; if (state == old_state) return; TRACE(D_EVENTS, "Session to %I changed state from %s to %s", s->addr, bfd_state_names[old_state], bfd_state_names[state]); bfd_lock_sessions(p); s->loc_state = state; s->loc_diag = diag; s->last_state_change = current_time(); notify = !NODE_VALID(&s->n); if (notify) add_tail(&p->notify_list, &s->n); bfd_unlock_sessions(p); if (state == BFD_STATE_UP) bfd_session_set_min_tx(s, s->cf.min_tx_int); if (old_state == BFD_STATE_UP) bfd_session_set_min_tx(s, s->cf.idle_tx_int); if (notify) ev_send(&global_event_list, &p->notify_event); } static void bfd_session_update_tx_interval(struct bfd_session *s) { u32 tx_int = MAX(s->des_min_tx_int, s->rem_min_rx_int); u32 tx_int_l = tx_int - (tx_int / 4); // 75 % u32 tx_int_h = tx_int - (tx_int / 10); // 90 % s->tx_timer->recurrent = tx_int_l; s->tx_timer->randomize = tx_int_h - tx_int_l; /* Do not set timer if no previous event */ if (!s->last_tx) return; /* Set timer relative to last tx_timer event */ tm_set_in(s->tx_timer, s->last_tx + tx_int_l, s->ifa->bfd->p.loop); } static void bfd_session_update_detection_time(struct bfd_session *s, int kick) { btime timeout = (btime) MAX(s->req_min_rx_int, s->rem_min_tx_int) * s->rem_detect_mult; if (kick) s->last_rx = current_time(); if (!s->last_rx) return; tm_set_in(s->hold_timer, s->last_rx + timeout, s->ifa->bfd->p.loop); } static void bfd_session_control_tx_timer(struct bfd_session *s, int reset) { // if (!s->opened) goto stop; if (s->passive && (s->rem_id == 0)) goto stop; if (s->rem_demand_mode && !s->poll_active && (s->loc_state == BFD_STATE_UP) && (s->rem_state == BFD_STATE_UP)) goto stop; if (s->rem_min_rx_int == 0) goto stop; /* So TX timer should run */ if (reset || !tm_active(s->tx_timer)) { s->last_tx = 0; tm_start_in(s->tx_timer, 0, s->ifa->bfd->p.loop); } return; stop: tm_stop(s->tx_timer); s->last_tx = 0; } static void bfd_session_request_poll(struct bfd_session *s, u8 request) { /* Not sure about this, but doing poll in this case does not make sense */ if (s->rem_id == 0) return; s->poll_scheduled |= request; if (s->poll_active) return; s->poll_active = s->poll_scheduled; s->poll_scheduled = 0; bfd_session_control_tx_timer(s, 1); } static void bfd_session_terminate_poll(struct bfd_session *s) { u8 poll_done = s->poll_active & ~s->poll_scheduled; if (poll_done & BFD_POLL_TX) s->des_min_tx_int = s->des_min_tx_new; if (poll_done & BFD_POLL_RX) s->req_min_rx_int = s->req_min_rx_new; s->poll_active = s->poll_scheduled; s->poll_scheduled = 0; /* Timers are updated by caller - bfd_session_process_ctl() */ } void bfd_session_process_ctl(struct bfd_session *s, u8 flags, u32 old_tx_int, u32 old_rx_int) { if (s->poll_active && (flags & BFD_FLAG_FINAL)) bfd_session_terminate_poll(s); if ((s->des_min_tx_int != old_tx_int) || (s->rem_min_rx_int != old_rx_int)) bfd_session_update_tx_interval(s); bfd_session_update_detection_time(s, 1); /* Update session state */ int next_state = 0; int diag = BFD_DIAG_NOTHING; switch (s->loc_state) { case BFD_STATE_ADMIN_DOWN: return; case BFD_STATE_DOWN: if (s->rem_state == BFD_STATE_DOWN) next_state = BFD_STATE_INIT; else if (s->rem_state == BFD_STATE_INIT) next_state = BFD_STATE_UP; break; case BFD_STATE_INIT: if (s->rem_state == BFD_STATE_ADMIN_DOWN) next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN; else if (s->rem_state >= BFD_STATE_INIT) next_state = BFD_STATE_UP; break; case BFD_STATE_UP: if (s->rem_state <= BFD_STATE_DOWN) next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN; break; } if (next_state) bfd_session_update_state(s, next_state, diag); bfd_session_control_tx_timer(s, 0); if (flags & BFD_FLAG_POLL) bfd_send_ctl(s->ifa->bfd, s, 1); } static void bfd_session_timeout(struct bfd_session *s) { struct bfd_proto *p = s->ifa->bfd; TRACE(D_EVENTS, "Session to %I expired", s->addr); s->rem_state = BFD_STATE_DOWN; s->rem_id = 0; s->rem_min_tx_int = 0; s->rem_min_rx_int = 1; s->rem_demand_mode = 0; s->rem_detect_mult = 0; s->rx_csn_known = 0; s->poll_active = 0; s->poll_scheduled = 0; bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_TIMEOUT); bfd_session_control_tx_timer(s, 1); } static void bfd_session_set_min_tx(struct bfd_session *s, u32 val) { /* Note that des_min_tx_int <= des_min_tx_new */ if (val == s->des_min_tx_new) return; s->des_min_tx_new = val; /* Postpone timer update if des_min_tx_int increases and the session is up */ if ((s->loc_state != BFD_STATE_UP) || (val < s->des_min_tx_int)) { s->des_min_tx_int = val; bfd_session_update_tx_interval(s); } bfd_session_request_poll(s, BFD_POLL_TX); } static void bfd_session_set_min_rx(struct bfd_session *s, u32 val) { /* Note that req_min_rx_int >= req_min_rx_new */ if (val == s->req_min_rx_new) return; s->req_min_rx_new = val; /* Postpone timer update if req_min_rx_int decreases and the session is up */ if ((s->loc_state != BFD_STATE_UP) || (val > s->req_min_rx_int)) { s->req_min_rx_int = val; bfd_session_update_detection_time(s, 0); } bfd_session_request_poll(s, BFD_POLL_RX); } struct bfd_session * bfd_find_session_by_id(struct bfd_proto *p, u32 id) { return HASH_FIND(p->session_hash_id, HASH_ID, id); } struct bfd_session * bfd_find_session_by_addr(struct bfd_proto *p, ip_addr addr, uint ifindex) { return HASH_FIND(p->session_hash_ip, HASH_IP, addr, ifindex); } static void bfd_tx_timer_hook(timer *t) { struct bfd_session *s = t->data; s->last_tx = current_time(); bfd_send_ctl(s->ifa->bfd, s, 0); } static void bfd_hold_timer_hook(timer *t) { bfd_session_timeout(t->data); } static u32 bfd_get_free_id(struct bfd_proto *p) { u32 id; for (id = random_u32(); 1; id++) if (id && !bfd_find_session_by_id(p, id)) break; return id; } static struct bfd_session * bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *iface, struct bfd_options *opts) { ASSERT_DIE(birdloop_inside(p->p.loop)); struct bfd_iface *ifa = bfd_get_iface(p, local, iface); struct bfd_session *s = sl_allocz(p->session_slab); s->addr = addr; s->ifa = ifa; s->ifindex = iface ? iface->index : 0; s->loc_id = bfd_get_free_id(p); HASH_INSERT(p->session_hash_id, HASH_ID, s); HASH_INSERT(p->session_hash_ip, HASH_IP, s); s->cf = bfd_merge_options(ifa->cf, opts); /* Initialization of state variables - see RFC 5880 6.8.1 */ s->loc_state = BFD_STATE_DOWN; s->rem_state = BFD_STATE_DOWN; s->des_min_tx_int = s->des_min_tx_new = s->cf.idle_tx_int; s->req_min_rx_int = s->req_min_rx_new = s->cf.min_rx_int; s->rem_min_rx_int = 1; s->detect_mult = s->cf.multiplier; s->passive = s->cf.passive; s->tx_csn = random_u32(); s->tx_timer = tm_new_init(p->tpool, bfd_tx_timer_hook, s, 0, 0); s->hold_timer = tm_new_init(p->tpool, bfd_hold_timer_hook, s, 0, 0); bfd_session_update_tx_interval(s); bfd_session_control_tx_timer(s, 1); init_list(&s->request_list); s->last_state_change = current_time(); TRACE(D_EVENTS, "Session to %I added", s->addr); return s; } /* static void bfd_open_session(struct bfd_proto *p, struct bfd_session *s, ip_addr local, struct iface *ifa) { birdloop_enter(p->p.loop); s->opened = 1; bfd_session_control_tx_timer(s); birdloop_leave(p->p.loop); } static void bfd_close_session(struct bfd_proto *p, struct bfd_session *s) { birdloop_enter(p->p.loop); s->opened = 0; bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_PATH_DOWN); bfd_session_control_tx_timer(s); birdloop_leave(p->p.loop); } */ static void bfd_remove_session_locked(struct bfd_proto *p, struct bfd_session *s) { /* Caller should ensure that request list is empty */ /* Remove session from notify list if scheduled for notification */ /* No need for bfd_lock_sessions(), we are already protected by birdloop_enter() */ if (NODE_VALID(&s->n)) rem_node(&s->n); bfd_free_iface(s->ifa); rfree(s->tx_timer); rfree(s->hold_timer); HASH_REMOVE(p->session_hash_id, HASH_ID, s); HASH_REMOVE(p->session_hash_ip, HASH_IP, s); TRACE(D_EVENTS, "Session to %I removed", s->addr); sl_free(s); } static void bfd_remove_session(struct bfd_proto *p, struct bfd_session *s) { birdloop_enter(p->p.loop); bfd_remove_session_locked(p, s); birdloop_leave(p->p.loop); } static void bfd_reconfigure_session(struct bfd_proto *p, struct bfd_session *s) { if (EMPTY_LIST(s->request_list)) return; birdloop_enter(p->p.loop); struct bfd_request *req = SKIP_BACK(struct bfd_request, n, HEAD(s->request_list)); s->cf = bfd_merge_options(s->ifa->cf, &req->opts); u32 tx = (s->loc_state == BFD_STATE_UP) ? s->cf.min_tx_int : s->cf.idle_tx_int; bfd_session_set_min_tx(s, tx); bfd_session_set_min_rx(s, s->cf.min_rx_int); s->detect_mult = s->cf.multiplier; s->passive = s->cf.passive; bfd_session_control_tx_timer(s, 0); birdloop_leave(p->p.loop); TRACE(D_EVENTS, "Session to %I reconfigured", s->addr); } /* * BFD interfaces */ static struct bfd_iface_config bfd_default_iface = { .min_rx_int = BFD_DEFAULT_MIN_RX_INT, .min_tx_int = BFD_DEFAULT_MIN_TX_INT, .idle_tx_int = BFD_DEFAULT_IDLE_TX_INT, .multiplier = BFD_DEFAULT_MULTIPLIER }; static inline struct bfd_iface_config * bfd_find_iface_config(struct bfd_config *cf, struct iface *iface) { struct bfd_iface_config *ic; ic = iface ? (void *) iface_patt_find(&cf->patt_list, iface, NULL) : cf->multihop; return ic ? ic : &bfd_default_iface; } static struct bfd_iface * bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface) { struct bfd_iface *ifa; WALK_LIST(ifa, p->iface_list) if (ipa_equal(ifa->local, local) && (ifa->iface == iface)) return ifa->uc++, ifa; struct bfd_config *cf = (struct bfd_config *) (p->p.cf); struct bfd_iface_config *ic = bfd_find_iface_config(cf, iface); ifa = mb_allocz(p->tpool, sizeof(struct bfd_iface)); ifa->local = local; ifa->iface = iface; ifa->cf = ic; ifa->bfd = p; ifa->sk = bfd_open_tx_sk(p, local, iface); ifa->uc = 1; if (cf->strict_bind) ifa->rx = bfd_open_rx_sk_bound(p, local, iface); add_tail(&p->iface_list, &ifa->n); return ifa; } static void bfd_free_iface(struct bfd_iface *ifa) { if (!ifa || --ifa->uc) return; if (ifa->sk) sk_close(ifa->sk); if (ifa->rx) sk_close(ifa->rx); rem_node(&ifa->n); mb_free(ifa); } static void bfd_reconfigure_iface(struct bfd_proto *p, struct bfd_iface *ifa, struct bfd_config *nc) { struct bfd_iface_config *new = bfd_find_iface_config(nc, ifa->iface); struct bfd_iface_config *old = ifa->cf; /* Check options that are handled in bfd_reconfigure_session() */ ifa->changed = (new->min_rx_int != old->min_rx_int) || (new->min_tx_int != old->min_tx_int) || (new->idle_tx_int != old->idle_tx_int) || (new->multiplier != old->multiplier) || (new->passive != old->passive); /* This should be probably changed to not access ifa->cf from the BFD thread */ birdloop_enter(p->p.loop); ifa->cf = new; birdloop_leave(p->p.loop); } /* * BFD requests */ static void bfd_request_notify(struct bfd_request *req, u8 state, u8 diag) { u8 old_state = req->state; if (state == old_state) return; req->state = state; req->diag = diag; req->old_state = old_state; req->down = (old_state == BFD_STATE_UP) && (state == BFD_STATE_DOWN); if (req->hook) { struct birdloop *target = !birdloop_inside(req->target) ? req->target : NULL; if (target) birdloop_enter(target); req->hook(req); if (target) birdloop_leave(target); } } static int bfd_add_request(struct bfd_proto *p, struct bfd_request *req) { struct bfd_config *cf = (struct bfd_config *) (p->p.cf); if (p->p.vrf && (p->p.vrf != req->vrf)) { TRACE(D_EVENTS, "Not accepting request to %I with different VRF", req->addr); return 0; } if (ipa_is_ip4(req->addr) ? !cf->accept_ipv4 : !cf->accept_ipv6) { TRACE(D_EVENTS, "Not accepting request to %I (AF limit)", req->addr); return 0; } if (req->iface ? !cf->accept_direct : !cf->accept_multihop) { TRACE(D_EVENTS, "Not accepting %s request to %I", req->iface ? "direct" : "multihop", req->addr); return 0; } uint ifindex = req->iface ? req->iface->index : 0; struct bfd_session *s = bfd_find_session_by_addr(p, req->addr, ifindex); if (s) TRACE(D_EVENTS, "Session to %I reused", s->addr); else s = bfd_add_session(p, req->addr, req->local, req->iface, &req->opts); rem_node(&req->n); add_tail(&s->request_list, &req->n); req->session = s; bfd_lock_sessions(p); int notify = !NODE_VALID(&s->n); if (notify) add_tail(&p->notify_list, &s->n); bfd_unlock_sessions(p); if (notify) ev_send(&global_event_list, &p->notify_event); return 1; } static void bfd_pickup_requests(void *_data UNUSED) { /* NOTE TO MY FUTURE SELF * * Functions bfd_take_requests() and bfd_drop_requests() need to have * consistent &bfd_global.wait_list and this is ensured only by having these * functions called from bfd_start() and bfd_shutdown() which are both called * in PROTO_LOCKED_FROM_MAIN context, i.e. always from &main_birdloop. * * This pickup event is also called in &main_birdloop, therefore we can * freely do BFD_LOCK/BFD_UNLOCK while processing all the requests. All BFD * protocols capable of bfd_add_request() are either started before this code * happens or after that. * * If BFD protocols could start in parallel with this routine, they might * miss some of the waiting requests, thus if anybody tries to start * protocols or run this pickup event outside &main_birdloop in future, they * shall ensure that this race condition is mitigated somehow. * * Thank you, my future self, for understanding. Have a nice day! */ DBG("BFD pickup loop starting"); BFD_LOCK; do { bfd_global.pickup_reload = 0; BFD_UNLOCK; node *n; WALK_LIST(n, bfd_global.proto_list) { struct bfd_proto *p = SKIP_BACK(struct bfd_proto, bfd_node, n); birdloop_enter(p->p.loop); BFD_LOCK; TRACE(D_EVENTS, "Picking up new requests (%d available)", list_length(&bfd_global.pickup_list)); node *rn, *rnxt; WALK_LIST_DELSAFE(rn, rnxt, bfd_global.pickup_list) bfd_add_request(p, SKIP_BACK(struct bfd_request, n, rn)); BFD_UNLOCK; /* Remove sessions with no requests */ HASH_WALK_DELSAFE(p->session_hash_id, next_id, s) { if (EMPTY_LIST(s->request_list)) bfd_remove_session_locked(p, s); } HASH_WALK_END; birdloop_leave(p->p.loop); } BFD_LOCK; } while (bfd_global.pickup_reload); list tmp_list; init_list(&tmp_list); add_tail_list(&tmp_list, &bfd_global.pickup_list); init_list(&bfd_global.pickup_list); BFD_UNLOCK; log(L_TRACE "No protocol for %d BFD requests", list_length(&tmp_list)); node *n; WALK_LIST(n, tmp_list) bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), BFD_STATE_ADMIN_DOWN, 0); BFD_LOCK; add_tail_list(&bfd_global.wait_list, &tmp_list); BFD_UNLOCK; } static event bfd_pickup_event = { .hook = bfd_pickup_requests }; static void bfd_take_requests(struct bfd_proto *p) { node *n, *nn; BFD_LOCK; WALK_LIST_DELSAFE(n, nn, bfd_global.wait_list) bfd_add_request(p, SKIP_BACK(struct bfd_request, n, n)); BFD_UNLOCK; } static void bfd_drop_requests(struct bfd_proto *p) { node *n; BFD_LOCK; HASH_WALK_DELSAFE(p->session_hash_id, next_id, s) { WALK_LIST_FIRST(n, s->request_list) { struct bfd_request *req = SKIP_BACK(struct bfd_request, n, n); rem_node(&req->n); add_tail(&bfd_global.pickup_list, &req->n); req->session = NULL; } ev_send(&global_event_list, &bfd_pickup_event); bfd_remove_session_locked(p, s); } HASH_WALK_END; BFD_UNLOCK; } static struct resclass bfd_request_class; struct bfd_request * bfd_request_session(pool *p, ip_addr addr, ip_addr local, struct iface *iface, struct iface *vrf, void (*hook)(struct bfd_request *), void *data, struct birdloop *target, const struct bfd_options *opts) { struct bfd_request *req = ralloc(p, &bfd_request_class); req->addr = addr; req->local = local; req->iface = iface; req->vrf = vrf; if (opts) req->opts = *opts; ASSERT_DIE(target || !hook); req->hook = hook; req->data = data; req->target = target; req->session = NULL; BFD_LOCK; bfd_global.pickup_reload++; add_tail(&bfd_global.pickup_list, &req->n); ev_send(&global_event_list, &bfd_pickup_event); DBG("New BFD request enlisted.\n"); BFD_UNLOCK; return req; } void bfd_update_request(struct bfd_request *req, const struct bfd_options *opts) { struct bfd_session *s = req->session; if (!memcmp(opts, &req->opts, sizeof(const struct bfd_options))) return; req->opts = *opts; if (s) bfd_reconfigure_session(s->ifa->bfd, s); } static void bfd_request_free(resource *r) { struct bfd_request *req = (struct bfd_request *) r; BFD_LOCK; rem_node(&req->n); BFD_UNLOCK; ev_send(&global_event_list, &bfd_pickup_event); } static void bfd_request_dump(resource *r, unsigned indent UNUSED) { struct bfd_request *req = (struct bfd_request *) r; debug("(code %p, data %p)\n", req->hook, req->data); } static struct resclass bfd_request_class = { "BFD request", sizeof(struct bfd_request), bfd_request_free, bfd_request_dump, NULL, NULL }; /* * BFD neighbors */ static void bfd_neigh_notify(struct neighbor *nb) { struct bfd_proto *p = (struct bfd_proto *) nb->proto; struct bfd_neighbor *n = nb->data; if (!n) return; if ((nb->scope > 0) && !n->req) { ip_addr local = ipa_nonzero(n->local) ? n->local : nb->ifa->ip; n->req = bfd_request_session(p->p.pool, n->addr, local, nb->iface, p->p.vrf, NULL, NULL, NULL, NULL); } if ((nb->scope <= 0) && n->req) { rfree(n->req); n->req = NULL; } } static void bfd_start_neighbor(struct bfd_proto *p, struct bfd_neighbor *n) { n->active = 1; if (n->multihop) { n->req = bfd_request_session(p->p.pool, n->addr, n->local, NULL, p->p.vrf, NULL, NULL, NULL, NULL); return; } struct neighbor *nb = neigh_find(&p->p, n->addr, n->iface, NEF_STICKY); if (!nb) { log(L_ERR "%s: Invalid remote address %I%J", p->p.name, n->addr, n->iface); return; } if (nb->data) { log(L_ERR "%s: Duplicate neighbor %I", p->p.name, n->addr); return; } n->neigh = nb; nb->data = n; if (nb->scope > 0) bfd_neigh_notify(nb); else TRACE(D_EVENTS, "Waiting for %I%J to become my neighbor", n->addr, n->iface); } static void bfd_stop_neighbor(struct bfd_proto *p UNUSED, struct bfd_neighbor *n) { if (n->neigh) n->neigh->data = NULL; n->neigh = NULL; rfree(n->req); n->req = NULL; } static inline int bfd_same_neighbor(struct bfd_neighbor *x, struct bfd_neighbor *y) { return ipa_equal(x->addr, y->addr) && ipa_equal(x->local, y->local) && (x->iface == y->iface) && (x->multihop == y->multihop); } static void bfd_reconfigure_neighbors(struct bfd_proto *p, struct bfd_config *new) { struct bfd_config *old = (struct bfd_config *) (p->p.cf); struct bfd_neighbor *on, *nn; WALK_LIST(on, old->neigh_list) { WALK_LIST(nn, new->neigh_list) if (bfd_same_neighbor(nn, on)) { nn->neigh = on->neigh; if (nn->neigh) nn->neigh->data = nn; nn->req = on->req; nn->active = 1; goto next; } bfd_stop_neighbor(p, on); next:; } WALK_LIST(nn, new->neigh_list) if (!nn->active) bfd_start_neighbor(p, nn); } /* * BFD notify socket */ /* This core notify code should be replaced after main loop transition to birdloop */ static void bfd_notify_hook(void *data) { struct bfd_proto *p = data; struct bfd_session *s; list tmp_list; u8 state, diag; node *n, *nn; bfd_lock_sessions(p); init_list(&tmp_list); add_tail_list(&tmp_list, &p->notify_list); init_list(&p->notify_list); bfd_unlock_sessions(p); WALK_LIST_FIRST(s, tmp_list) { bfd_lock_sessions(p); rem_node(&s->n); state = s->loc_state; diag = s->loc_diag; bfd_unlock_sessions(p); WALK_LIST_DELSAFE(n, nn, s->request_list) bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), state, diag); /* Remove the session if all requests were removed in notify hooks */ if (EMPTY_LIST(s->request_list)) bfd_remove_session(p, s); } } /* * BFD protocol glue */ static struct proto * bfd_init(struct proto_config *c) { struct proto *p = proto_new(c); p->iface_sub.neigh_notify = bfd_neigh_notify; return p; } static int bfd_start(struct proto *P) { struct bfd_proto *p = (struct bfd_proto *) P; struct bfd_config *cf = (struct bfd_config *) (P->cf); pthread_spin_init(&p->lock, PTHREAD_PROCESS_PRIVATE); p->tpool = birdloop_pool(P->loop); p->session_slab = sl_new(P->pool, sizeof(struct bfd_session)); HASH_INIT(p->session_hash_id, P->pool, 8); HASH_INIT(p->session_hash_ip, P->pool, 8); init_list(&p->iface_list); init_list(&p->notify_list); p->notify_event = (event) { .hook = bfd_notify_hook, .data = p, }; add_tail(&bfd_global.proto_list, &p->bfd_node); if (!cf->strict_bind) { if (cf->accept_ipv4 && cf->accept_direct) p->rx4_1 = bfd_open_rx_sk(p, 0, SK_IPV4); if (cf->accept_ipv4 && cf->accept_multihop) p->rx4_m = bfd_open_rx_sk(p, 1, SK_IPV4); if (cf->accept_ipv6 && cf->accept_direct) p->rx6_1 = bfd_open_rx_sk(p, 0, SK_IPV6); if (cf->accept_ipv6 && cf->accept_multihop) p->rx6_m = bfd_open_rx_sk(p, 1, SK_IPV6); } bfd_take_requests(p); struct bfd_neighbor *n; WALK_LIST(n, cf->neigh_list) bfd_start_neighbor(p, n); return PS_UP; } static int bfd_shutdown(struct proto *P) { struct bfd_proto *p = (struct bfd_proto *) P; struct bfd_config *cf = (struct bfd_config *) (p->p.cf); rem_node(&p->bfd_node); struct bfd_neighbor *bn; WALK_LIST(bn, cf->neigh_list) bfd_stop_neighbor(p, bn); bfd_drop_requests(p); return PS_DOWN; } static int bfd_reconfigure(struct proto *P, struct proto_config *c) { struct bfd_proto *p = (struct bfd_proto *) P; struct bfd_config *old = (struct bfd_config *) (P->cf); struct bfd_config *new = (struct bfd_config *) c; struct bfd_iface *ifa; /* TODO: Improve accept reconfiguration */ if ((new->accept_ipv4 != old->accept_ipv4) || (new->accept_ipv6 != old->accept_ipv6) || (new->accept_direct != old->accept_direct) || (new->accept_multihop != old->accept_multihop) || (new->strict_bind != old->strict_bind)) return 0; birdloop_mask_wakeups(p->p.loop); WALK_LIST(ifa, p->iface_list) bfd_reconfigure_iface(p, ifa, new); HASH_WALK(p->session_hash_id, next_id, s) { if (s->ifa->changed) bfd_reconfigure_session(p, s); } HASH_WALK_END; bfd_reconfigure_neighbors(p, new); birdloop_unmask_wakeups(p->p.loop); return 1; } static void bfd_copy_config(struct proto_config *dest, struct proto_config *src UNUSED) { struct bfd_config *d = (struct bfd_config *) dest; // struct bfd_config *s = (struct bfd_config *) src; /* We clean up patt_list and neigh_list, neighbors and ifaces are non-sharable */ init_list(&d->patt_list); init_list(&d->neigh_list); } void bfd_show_sessions(struct proto *P) { byte tbuf[TM_DATETIME_BUFFER_SIZE]; struct bfd_proto *p = (struct bfd_proto *) P; uint state, diag UNUSED; btime tx_int, timeout; const char *ifname; if (p->p.proto_state != PS_UP) { cli_msg(-1020, "%s: is not up", p->p.name); return; } cli_msg(-1020, "%s:", p->p.name); cli_msg(-1020, "%-25s %-10s %-10s %-12s %8s %8s", "IP address", "Interface", "State", "Since", "Interval", "Timeout"); HASH_WALK(p->session_hash_id, next_id, s) { /* FIXME: this is thread-unsafe, but perhaps harmless */ state = s->loc_state; diag = s->loc_diag; ifname = (s->ifa && s->ifa->iface) ? s->ifa->iface->name : "---"; tx_int = s->last_tx ? MAX(s->des_min_tx_int, s->rem_min_rx_int) : 0; timeout = (btime) MAX(s->req_min_rx_int, s->rem_min_tx_int) * s->rem_detect_mult; state = (state < 4) ? state : 0; tm_format_time(tbuf, &config->tf_proto, s->last_state_change); cli_msg(-1020, "%-25I %-10s %-10s %-12s %7t %7t", s->addr, ifname, bfd_state_names[state], tbuf, tx_int, timeout); } HASH_WALK_END; } struct protocol proto_bfd = { .name = "BFD", .template = "bfd%d", .proto_size = sizeof(struct bfd_proto), .config_size = sizeof(struct bfd_config), .init = bfd_init, .start = bfd_start, .shutdown = bfd_shutdown, .reconfigure = bfd_reconfigure, .copy_config = bfd_copy_config, }; void bfd_build(void) { proto_build(&proto_bfd); bfd_global.lock = DOMAIN_NEW(rtable, "BFD Global"); init_list(&bfd_global.wait_list); init_list(&bfd_global.pickup_list); init_list(&bfd_global.proto_list); }