I still need an ok for this diff. It is the final step before we
can run IP forwaring in parallel.
bluhm
On Thu, Apr 21, 2022 at 05:44:17PM +0200, Alexander Bluhm wrote:
> On Wed, Apr 20, 2022 at 08:12:51PM +0200, Alexander Bluhm wrote:
> > mvs@ reminded me of a crash I have seen in December. Route timers
> > are not MP safe, but I think this can be fixed with a mutex. The
> > idea is to protect the global lists with a mutex and move the rttimer
> > into a temporary list. Then the callback and pool put can be called
> > later without mutex.
>
> I have a tiny update to the diff.
>
> - Global locks are documented with capital letter, so use [T].
>
> - rt_timer_add() grabbed the mutex twice, first remove then add.
> Better exchange in one critical section. pool_get before and
> pool_put after.
>
> ok?
>
> Index: net/route.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/net/route.c,v
> retrieving revision 1.406
> diff -u -p -r1.406 route.c
> --- net/route.c 20 Apr 2022 17:58:22 -0000 1.406
> +++ net/route.c 21 Apr 2022 13:31:52 -0000
> @@ -1361,7 +1361,8 @@ rt_ifa_purge_walker(struct rtentry *rt,
> * for multiple queues for efficiency's sake...
> */
>
> -LIST_HEAD(, rttimer_queue) rttimer_queue_head;
> +struct mutex rttimer_mtx;
> +LIST_HEAD(, rttimer_queue) rttimer_queue_head; /* [T] */
>
> #define RTTIMER_CALLOUT(r) { \
> if (r->rtt_func != NULL) { \
> @@ -1393,6 +1394,7 @@ rt_timer_init(void)
> pool_init(&rttimer_queue_pool, sizeof(struct rttimer_queue), 0,
> IPL_MPFLOOR, 0, "rttmrq", NULL);
>
> + mtx_init(&rttimer_mtx, IPL_MPFLOOR);
> LIST_INIT(&rttimer_queue_head);
> timeout_set_proc(&rt_timer_timeout, rt_timer_timer, &rt_timer_timeout);
> timeout_add_sec(&rt_timer_timeout, 1);
> @@ -1408,7 +1410,10 @@ rt_timer_queue_create(int timeout)
> rtq->rtq_timeout = timeout;
> rtq->rtq_count = 0;
> TAILQ_INIT(&rtq->rtq_head);
> +
> + mtx_enter(&rttimer_mtx);
> LIST_INSERT_HEAD(&rttimer_queue_head, rtq, rtq_link);
> + mtx_leave(&rttimer_mtx);
>
> return (rtq);
> }
> @@ -1416,28 +1421,36 @@ rt_timer_queue_create(int timeout)
> void
> rt_timer_queue_change(struct rttimer_queue *rtq, int timeout)
> {
> + mtx_enter(&rttimer_mtx);
> rtq->rtq_timeout = timeout;
> + mtx_leave(&rttimer_mtx);
> }
>
> void
> rt_timer_queue_destroy(struct rttimer_queue *rtq)
> {
> - struct rttimer *r;
> + struct rttimer *r;
> + TAILQ_HEAD(, rttimer) rttlist;
>
> NET_ASSERT_LOCKED();
>
> + TAILQ_INIT(&rttlist);
> + mtx_enter(&rttimer_mtx);
> while ((r = TAILQ_FIRST(&rtq->rtq_head)) != NULL) {
> LIST_REMOVE(r, rtt_link);
> TAILQ_REMOVE(&rtq->rtq_head, r, rtt_next);
> + TAILQ_INSERT_TAIL(&rttlist, r, rtt_next);
> + KASSERT(rtq->rtq_count > 0);
> + rtq->rtq_count--;
> + }
> + LIST_REMOVE(rtq, rtq_link);
> + mtx_leave(&rttimer_mtx);
> +
> + while ((r = TAILQ_FIRST(&rttlist)) != NULL) {
> + TAILQ_REMOVE(&rttlist, r, rtt_next);
> RTTIMER_CALLOUT(r);
> pool_put(&rttimer_pool, r);
> - if (rtq->rtq_count > 0)
> - rtq->rtq_count--;
> - else
> - printf("rt_timer_queue_destroy: rtq_count reached 0\n");
> }
> -
> - LIST_REMOVE(rtq, rtq_link);
> pool_put(&rttimer_queue_pool, rtq);
> }
>
> @@ -1450,15 +1463,22 @@ rt_timer_queue_count(struct rttimer_queu
> void
> rt_timer_remove_all(struct rtentry *rt)
> {
> - struct rttimer *r;
> + struct rttimer *r;
> + TAILQ_HEAD(, rttimer) rttlist;
>
> + TAILQ_INIT(&rttlist);
> + mtx_enter(&rttimer_mtx);
> while ((r = LIST_FIRST(&rt->rt_timer)) != NULL) {
> LIST_REMOVE(r, rtt_link);
> TAILQ_REMOVE(&r->rtt_queue->rtq_head, r, rtt_next);
> - if (r->rtt_queue->rtq_count > 0)
> - r->rtt_queue->rtq_count--;
> - else
> - printf("rt_timer_remove_all: rtq_count reached 0\n");
> + TAILQ_INSERT_TAIL(&rttlist, r, rtt_next);
> + KASSERT(r->rtt_queue->rtq_count > 0);
> + r->rtt_queue->rtq_count--;
> + }
> + mtx_leave(&rttimer_mtx);
> +
> + while ((r = TAILQ_FIRST(&rttlist)) != NULL) {
> + TAILQ_REMOVE(&rttlist, r, rtt_next);
> pool_put(&rttimer_pool, r);
> }
> }
> @@ -1467,12 +1487,23 @@ int
> rt_timer_add(struct rtentry *rt, void (*func)(struct rtentry *,
> struct rttimer *), struct rttimer_queue *queue, u_int rtableid)
> {
> - struct rttimer *r;
> + struct rttimer *r, *rnew;
> time_t current_time;
>
> + rnew = pool_get(&rttimer_pool, PR_NOWAIT | PR_ZERO);
> + if (rnew == NULL)
> + return (ENOBUFS);
> +
> current_time = getuptime();
> - rt->rt_expire = current_time + queue->rtq_timeout;
>
> + rnew->rtt_rt = rt;
> + rnew->rtt_time = current_time;
> + rnew->rtt_func = func;
> + rnew->rtt_queue = queue;
> + rnew->rtt_tableid = rtableid;
> +
> + mtx_enter(&rttimer_mtx);
> + rt->rt_expire = current_time + queue->rtq_timeout;
> /*
> * If there's already a timer with this action, destroy it before
> * we add a new one.
> @@ -1481,27 +1512,19 @@ rt_timer_add(struct rtentry *rt, void (*
> if (r->rtt_func == func) {
> LIST_REMOVE(r, rtt_link);
> TAILQ_REMOVE(&r->rtt_queue->rtq_head, r, rtt_next);
> - if (r->rtt_queue->rtq_count > 0)
> - r->rtt_queue->rtq_count--;
> - else
> - printf("rt_timer_add: rtq_count reached 0\n");
> - pool_put(&rttimer_pool, r);
> + KASSERT(r->rtt_queue->rtq_count > 0);
> + r->rtt_queue->rtq_count--;
> break; /* only one per list, so we can quit... */
> }
> }
>
> - r = pool_get(&rttimer_pool, PR_NOWAIT | PR_ZERO);
> - if (r == NULL)
> - return (ENOBUFS);
> + LIST_INSERT_HEAD(&rt->rt_timer, rnew, rtt_link);
> + TAILQ_INSERT_TAIL(&queue->rtq_head, rnew, rtt_next);
> + rnew->rtt_queue->rtq_count++;
> + mtx_leave(&rttimer_mtx);
>
> - r->rtt_rt = rt;
> - r->rtt_time = current_time;
> - r->rtt_func = func;
> - r->rtt_queue = queue;
> - r->rtt_tableid = rtableid;
> - LIST_INSERT_HEAD(&rt->rt_timer, r, rtt_link);
> - TAILQ_INSERT_TAIL(&queue->rtq_head, r, rtt_next);
> - r->rtt_queue->rtq_count++;
> + if (r != NULL)
> + pool_put(&rttimer_pool, r);
>
> return (0);
> }
> @@ -1512,23 +1535,30 @@ rt_timer_timer(void *arg)
> struct timeout *to = (struct timeout *)arg;
> struct rttimer_queue *rtq;
> struct rttimer *r;
> + TAILQ_HEAD(, rttimer) rttlist;
> time_t current_time;
>
> current_time = getuptime();
> + TAILQ_INIT(&rttlist);
>
> NET_LOCK();
> + mtx_enter(&rttimer_mtx);
> LIST_FOREACH(rtq, &rttimer_queue_head, rtq_link) {
> while ((r = TAILQ_FIRST(&rtq->rtq_head)) != NULL &&
> (r->rtt_time + rtq->rtq_timeout) < current_time) {
> LIST_REMOVE(r, rtt_link);
> TAILQ_REMOVE(&rtq->rtq_head, r, rtt_next);
> - RTTIMER_CALLOUT(r);
> - pool_put(&rttimer_pool, r);
> - if (rtq->rtq_count > 0)
> - rtq->rtq_count--;
> - else
> - printf("rt_timer_timer: rtq_count reached 0\n");
> + TAILQ_INSERT_TAIL(&rttlist, r, rtt_next);
> + KASSERT(rtq->rtq_count > 0);
> + rtq->rtq_count--;
> }
> + }
> + mtx_leave(&rttimer_mtx);
> +
> + while ((r = TAILQ_FIRST(&rttlist)) != NULL) {
> + TAILQ_REMOVE(&rttlist, r, rtt_next);
> + RTTIMER_CALLOUT(r);
> + pool_put(&rttimer_pool, r);
> }
> NET_UNLOCK();
>
> Index: net/route.h
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/net/route.h,v
> retrieving revision 1.190
> diff -u -p -r1.190 route.h
> --- net/route.h 20 Apr 2022 17:58:22 -0000 1.190
> +++ net/route.h 21 Apr 2022 13:26:18 -0000
> @@ -36,6 +36,12 @@
> #define _NET_ROUTE_H_
>
> /*
> + * Locks used to protect struct members in this file:
> + * I immutable after creation
> + * T rttimer_mtx route timer lists
> + */
> +
> +/*
> * Kernel resident routing tables.
> *
> * The routing tables are initialized when interface addresses
> @@ -400,21 +406,21 @@ rtstat_inc(enum rtstat_counters c)
> * These allow functions to be called for routes at specific times.
> */
> struct rttimer {
> - TAILQ_ENTRY(rttimer) rtt_next; /* entry on timer queue */
> - LIST_ENTRY(rttimer) rtt_link; /* multiple timers per rtentry */
> - struct rttimer_queue *rtt_queue;/* back pointer to queue */
> - struct rtentry *rtt_rt; /* Back pointer to the route */
> - void (*rtt_func)(struct rtentry *,
> - struct rttimer *);
> - time_t rtt_time; /* When this timer was registered */
> - u_int rtt_tableid; /* routing table id of rtt_rt */
> + TAILQ_ENTRY(rttimer) rtt_next; /* [T] entry on timer queue */
> + LIST_ENTRY(rttimer) rtt_link; /* [T] timers per rtentry */
> + struct rttimer_queue *rtt_queue; /* [T] back pointer to queue */
> + struct rtentry *rtt_rt; /* [I] back pointer to route */
> + void (*rtt_func) /* [I] callback */
> + (struct rtentry *, struct rttimer *);
> + time_t rtt_time; /* [I] when timer registered */
> + u_int rtt_tableid; /* [I] rtable id of rtt_rt */
> };
>
> struct rttimer_queue {
> - TAILQ_HEAD(, rttimer) rtq_head;
> - LIST_ENTRY(rttimer_queue) rtq_link;
> - unsigned long rtq_count;
> - int rtq_timeout;
> + TAILQ_HEAD(, rttimer) rtq_head; /* [T] */
> + LIST_ENTRY(rttimer_queue) rtq_link; /* [T] */
> + unsigned long rtq_count; /* [T] */
> + int rtq_timeout; /* [T] */
> };
>
> const char *rtlabel_id2name(u_int16_t);