summaryrefslogtreecommitdiff
path: root/proto/bfd/bfd.c
diff options
context:
space:
mode:
Diffstat (limited to 'proto/bfd/bfd.c')
-rw-r--r--proto/bfd/bfd.c347
1 files changed, 193 insertions, 154 deletions
diff --git a/proto/bfd/bfd.c b/proto/bfd/bfd.c
index 873e2ed5..c88c1cb2 100644
--- a/proto/bfd/bfd.c
+++ b/proto/bfd/bfd.c
@@ -82,7 +82,7 @@
* 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
- * bfd_notify_kick() and a pipe. The hook then processes scheduled sessions and
+ * 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().
@@ -113,15 +113,22 @@
#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)
-static list STATIC_LIST_INIT(bfd_proto_list);
-static list STATIC_LIST_INIT(bfd_wait_list);
+#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);
-static inline void bfd_notify_kick(struct bfd_proto *p);
/*
@@ -170,7 +177,7 @@ bfd_session_update_state(struct bfd_session *s, uint state, uint diag)
bfd_session_set_min_tx(s, s->cf.idle_tx_int);
if (notify)
- bfd_notify_kick(p);
+ ev_send(&global_event_list, &p->notify_event);
}
static void
@@ -188,7 +195,7 @@ bfd_session_update_tx_interval(struct bfd_session *s)
return;
/* Set timer relative to last tx_timer event */
- tm_set(s->tx_timer, s->last_tx + tx_int_l);
+ tm_set_in(s->tx_timer, s->last_tx + tx_int_l, s->ifa->bfd->p.loop);
}
static void
@@ -202,7 +209,7 @@ bfd_session_update_detection_time(struct bfd_session *s, int kick)
if (!s->last_rx)
return;
- tm_set(s->hold_timer, s->last_rx + timeout);
+ tm_set_in(s->hold_timer, s->last_rx + timeout, s->ifa->bfd->p.loop);
}
static void
@@ -226,7 +233,7 @@ bfd_session_control_tx_timer(struct bfd_session *s, int reset)
if (reset || !tm_active(s->tx_timer))
{
s->last_tx = 0;
- tm_start(s->tx_timer, 0);
+ tm_start_in(s->tx_timer, 0, s->ifa->bfd->p.loop);
}
return;
@@ -419,7 +426,7 @@ bfd_get_free_id(struct bfd_proto *p)
static struct bfd_session *
bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *iface, struct bfd_options *opts)
{
- birdloop_enter(p->loop);
+ ASSERT_DIE(birdloop_inside(p->p.loop));
struct bfd_iface *ifa = bfd_get_iface(p, local, iface);
@@ -454,8 +461,6 @@ bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *
TRACE(D_EVENTS, "Session to %I added", s->addr);
- birdloop_leave(p->loop);
-
return s;
}
@@ -463,38 +468,34 @@ bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *
static void
bfd_open_session(struct bfd_proto *p, struct bfd_session *s, ip_addr local, struct iface *ifa)
{
- birdloop_enter(p->loop);
+ birdloop_enter(p->p.loop);
s->opened = 1;
bfd_session_control_tx_timer(s);
- birdloop_leave(p->loop);
+ birdloop_leave(p->p.loop);
}
static void
bfd_close_session(struct bfd_proto *p, struct bfd_session *s)
{
- birdloop_enter(p->loop);
+ 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->loop);
+ birdloop_leave(p->p.loop);
}
*/
static void
-bfd_remove_session(struct bfd_proto *p, struct bfd_session *s)
+bfd_remove_session_locked(struct bfd_proto *p, struct bfd_session *s)
{
- ip_addr ip = s->addr;
-
/* Caller should ensure that request list is empty */
- birdloop_enter(p->loop);
-
/* 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))
@@ -508,11 +509,17 @@ bfd_remove_session(struct bfd_proto *p, struct bfd_session *s)
HASH_REMOVE(p->session_hash_id, HASH_ID, s);
HASH_REMOVE(p->session_hash_ip, HASH_IP, s);
- sl_free(s);
+ TRACE(D_EVENTS, "Session to %I removed", s->addr);
- TRACE(D_EVENTS, "Session to %I removed", ip);
+ sl_free(s);
+}
- birdloop_leave(p->loop);
+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
@@ -521,7 +528,7 @@ bfd_reconfigure_session(struct bfd_proto *p, struct bfd_session *s)
if (EMPTY_LIST(s->request_list))
return;
- birdloop_enter(p->loop);
+ 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);
@@ -534,7 +541,7 @@ bfd_reconfigure_session(struct bfd_proto *p, struct bfd_session *s)
bfd_session_control_tx_timer(s, 0);
- birdloop_leave(p->loop);
+ birdloop_leave(p->p.loop);
TRACE(D_EVENTS, "Session to %I reconfigured", s->addr);
}
@@ -597,16 +604,10 @@ bfd_free_iface(struct bfd_iface *ifa)
return;
if (ifa->sk)
- {
- sk_stop(ifa->sk);
rfree(ifa->sk);
- }
if (ifa->rx)
- {
- sk_stop(ifa->rx);
rfree(ifa->rx);
- }
rem_node(&ifa->n);
mb_free(ifa);
@@ -627,9 +628,9 @@ bfd_reconfigure_iface(struct bfd_proto *p, struct bfd_iface *ifa, struct bfd_con
(new->passive != old->passive);
/* This should be probably changed to not access ifa->cf from the BFD thread */
- birdloop_enter(p->loop);
+ birdloop_enter(p->p.loop);
ifa->cf = new;
- birdloop_leave(p->loop);
+ birdloop_leave(p->p.loop);
}
@@ -651,7 +652,17 @@ bfd_request_notify(struct bfd_request *req, u8 state, u8 diag)
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
@@ -659,20 +670,30 @@ 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_set && (p->p.vrf != req->vrf))
+ 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);
- u8 state, diag;
- if (!s)
+ 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);
@@ -680,51 +701,129 @@ bfd_add_request(struct bfd_proto *p, struct bfd_request *req)
req->session = s;
bfd_lock_sessions(p);
- state = s->loc_state;
- diag = s->loc_diag;
+
+ int notify = !NODE_VALID(&s->n);
+ if (notify)
+ add_tail(&p->notify_list, &s->n);
+
bfd_unlock_sessions(p);
- bfd_request_notify(req, state, diag);
+ if (notify)
+ ev_send(&global_event_list, &p->notify_event);
return 1;
}
static void
-bfd_submit_request(struct bfd_request *req)
+bfd_pickup_requests(void *_data UNUSED)
{
- node *n;
+ /* 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;
- WALK_LIST(n, bfd_proto_list)
- if (bfd_add_request(SKIP_BACK(struct bfd_proto, bfd_node, n), req))
- return;
+ birdloop_leave(p->p.loop);
+ }
- rem_node(&req->n);
- add_tail(&bfd_wait_list, &req->n);
- req->session = NULL;
- bfd_request_notify(req, BFD_STATE_ADMIN_DOWN, 0);
+ 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;
-
- WALK_LIST_DELSAFE(n, nn, bfd_wait_list)
+ 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;
-
- HASH_WALK(p->session_hash_id, next_id, s)
+ BFD_LOCK;
+ HASH_WALK_DELSAFE(p->session_hash_id, next_id, s)
{
- /* We assume that p is not in bfd_proto_list */
WALK_LIST_FIRST(n, s->request_list)
- bfd_submit_request(SKIP_BACK(struct bfd_request, n, n));
+ {
+ 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;
@@ -733,13 +832,11 @@ 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);
- /* Hack: self-link req->n, we will call rem_node() on it */
- req->n.prev = req->n.next = &req->n;
-
req->addr = addr;
req->local = local;
req->iface = iface;
@@ -748,10 +845,19 @@ bfd_request_session(pool *p, ip_addr addr, ip_addr local,
if (opts)
req->opts = *opts;
- bfd_submit_request(req);
-
+ 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;
}
@@ -774,19 +880,16 @@ static void
bfd_request_free(resource *r)
{
struct bfd_request *req = (struct bfd_request *) r;
- struct bfd_session *s = req->session;
+ BFD_LOCK;
rem_node(&req->n);
+ BFD_UNLOCK;
- /* Remove the session if there is no request for it. Skip that if
- inside notify hooks, will be handled by bfd_notify_hook() itself */
-
- if (s && EMPTY_LIST(s->request_list) && !s->notify_running)
- bfd_remove_session(s->ifa->bfd, s);
+ ev_send(&global_event_list, &bfd_pickup_event);
}
static void
-bfd_request_dump(resource *r)
+bfd_request_dump(resource *r, unsigned indent UNUSED)
{
struct bfd_request *req = (struct bfd_request *) r;
@@ -819,7 +922,7 @@ bfd_neigh_notify(struct neighbor *nb)
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);
+ 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)
@@ -836,7 +939,7 @@ bfd_start_neighbor(struct bfd_proto *p, struct bfd_neighbor *n)
if (n->multihop)
{
- n->req = bfd_request_session(p->p.pool, n->addr, n->local, NULL, p->p.vrf, NULL, NULL, NULL);
+ n->req = bfd_request_session(p->p.pool, n->addr, n->local, NULL, p->p.vrf, NULL, NULL, NULL, NULL);
return;
}
@@ -916,21 +1019,15 @@ bfd_reconfigure_neighbors(struct bfd_proto *p, struct bfd_config *new)
/* This core notify code should be replaced after main loop transition to birdloop */
-int pipe(int pipefd[2]);
-void pipe_drain(int fd);
-void pipe_kick(int fd);
-
-static int
-bfd_notify_hook(sock *sk, uint len UNUSED)
+static void
+bfd_notify_hook(void *data)
{
- struct bfd_proto *p = sk->data;
+ struct bfd_proto *p = data;
struct bfd_session *s;
list tmp_list;
u8 state, diag;
node *n, *nn;
- pipe_drain(sk->fd);
-
bfd_lock_sessions(p);
init_list(&tmp_list);
add_tail_list(&tmp_list, &p->notify_list);
@@ -945,64 +1042,15 @@ bfd_notify_hook(sock *sk, uint len UNUSED)
diag = s->loc_diag;
bfd_unlock_sessions(p);
- s->notify_running = 1;
WALK_LIST_DELSAFE(n, nn, s->request_list)
bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), state, diag);
- s->notify_running = 0;
/* Remove the session if all requests were removed in notify hooks */
if (EMPTY_LIST(s->request_list))
bfd_remove_session(p, s);
}
-
- return 0;
}
-static inline void
-bfd_notify_kick(struct bfd_proto *p)
-{
- pipe_kick(p->notify_ws->fd);
-}
-
-static void
-bfd_noterr_hook(sock *sk, int err)
-{
- struct bfd_proto *p = sk->data;
- log(L_ERR "%s: Notify socket error: %m", p->p.name, err);
-}
-
-static void
-bfd_notify_init(struct bfd_proto *p)
-{
- int pfds[2];
- sock *sk;
-
- int rv = pipe(pfds);
- if (rv < 0)
- die("pipe: %m");
-
- sk = sk_new(p->p.pool);
- sk->type = SK_MAGIC;
- sk->rx_hook = bfd_notify_hook;
- sk->err_hook = bfd_noterr_hook;
- sk->fd = pfds[0];
- sk->data = p;
- if (sk_open(sk) < 0)
- die("bfd: sk_open failed");
- p->notify_rs = sk;
-
- /* The write sock is not added to any event loop */
- sk = sk_new(p->p.pool);
- sk->type = SK_MAGIC;
- sk->fd = pfds[1];
- sk->data = p;
- sk->flags = SKF_THREAD;
- if (sk_open(sk) < 0)
- die("bfd: sk_open failed");
- p->notify_ws = sk;
-}
-
-
/*
* BFD protocol glue
*/
@@ -1012,7 +1060,7 @@ bfd_init(struct proto_config *c)
{
struct proto *p = proto_new(c);
- p->neigh_notify = bfd_neigh_notify;
+ p->iface_sub.neigh_notify = bfd_neigh_notify;
return p;
}
@@ -1023,10 +1071,10 @@ bfd_start(struct proto *P)
struct bfd_proto *p = (struct bfd_proto *) P;
struct bfd_config *cf = (struct bfd_config *) (P->cf);
- p->loop = birdloop_new();
- p->tpool = rp_new(NULL, "BFD thread root");
pthread_spin_init(&p->lock, PTHREAD_PROCESS_PRIVATE);
+ p->tpool = rp_new(P->pool, "BFD loop pool");
+
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);
@@ -1034,11 +1082,12 @@ bfd_start(struct proto *P)
init_list(&p->iface_list);
init_list(&p->notify_list);
- bfd_notify_init(p);
-
- add_tail(&bfd_proto_list, &p->bfd_node);
+ p->notify_event = (event) {
+ .hook = bfd_notify_hook,
+ .data = p,
+ };
- birdloop_enter(p->loop);
+ add_tail(&bfd_global.proto_list, &p->bfd_node);
if (!cf->strict_bind)
{
@@ -1055,43 +1104,29 @@ bfd_start(struct proto *P)
p->rx6_m = bfd_open_rx_sk(p, 1, SK_IPV6);
}
- birdloop_leave(p->loop);
-
bfd_take_requests(p);
struct bfd_neighbor *n;
WALK_LIST(n, cf->neigh_list)
bfd_start_neighbor(p, n);
- birdloop_start(p->loop);
-
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->cf);
+ struct bfd_config *cf = (struct bfd_config *) (p->p.cf);
rem_node(&p->bfd_node);
- birdloop_stop(p->loop);
-
- struct bfd_neighbor *n;
- WALK_LIST(n, cf->neigh_list)
- bfd_stop_neighbor(p, n);
+ struct bfd_neighbor *bn;
+ WALK_LIST(bn, cf->neigh_list)
+ bfd_stop_neighbor(p, bn);
bfd_drop_requests(p);
- /* FIXME: This is hack */
- birdloop_enter(p->loop);
- rfree(p->tpool);
- birdloop_leave(p->loop);
-
- birdloop_free(p->loop);
-
return PS_DOWN;
}
@@ -1111,7 +1146,7 @@ bfd_reconfigure(struct proto *P, struct proto_config *c)
(new->strict_bind != old->strict_bind))
return 0;
- birdloop_mask_wakeups(p->loop);
+ birdloop_mask_wakeups(p->p.loop);
WALK_LIST(ifa, p->iface_list)
bfd_reconfigure_iface(p, ifa, new);
@@ -1125,7 +1160,7 @@ bfd_reconfigure(struct proto *P, struct proto_config *c)
bfd_reconfigure_neighbors(p, new);
- birdloop_unmask_wakeups(p->loop);
+ birdloop_unmask_wakeups(p->p.loop);
return 1;
}
@@ -1183,7 +1218,6 @@ bfd_show_sessions(struct proto *P)
struct protocol proto_bfd = {
.name = "BFD",
.template = "bfd%d",
- .class = PROTOCOL_BFD,
.proto_size = sizeof(struct bfd_proto),
.config_size = sizeof(struct bfd_config),
.init = bfd_init,
@@ -1197,4 +1231,9 @@ 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);
}