On Wed, Apr 20, 2022 at 08:12:51PM +0200, Alexander Bluhm wrote:
> Hi,
>
> 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.
>
> It survived a full regress with witness.
>
> Hrvoje: Can you put this on your test machine together with parallel
> IP forwarding?
>
> ok?
I would have prefer if the code was refactored first. The removal of a
rt_timer is copied over in 4 or 5 places with almost no difference.
In the rt_timer_queue_destroy() case you can use TAILQ_CONCAT and kill one
loop. Also I would use TAILQ_HEAD_INITIALIZER() instead of TAILQ_INIT().
To be honest most of this code should be directly replaced with the
timeout API. I bet the timeout API is more efficent.
> Index: net/route.c
> ===================================================================
> RCS file: /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 20 Apr 2022 18:00:39 -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);
> }
> }
> @@ -1471,8 +1491,9 @@ rt_timer_add(struct rtentry *rt, void (*
> time_t current_time;
>
> current_time = getuptime();
> - rt->rt_expire = current_time + queue->rtq_timeout;
>
> + 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 +1502,31 @@ 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... */
> }
> }
> + mtx_leave(&rttimer_mtx);
>
> - r = pool_get(&rttimer_pool, PR_NOWAIT | PR_ZERO);
> - if (r == NULL)
> - return (ENOBUFS);
> + if (r == NULL) {
> + r = pool_get(&rttimer_pool, PR_NOWAIT | PR_ZERO);
> + if (r == NULL)
> + return (ENOBUFS);
> + } else
> + memset(r, 0, sizeof(*r));
>
> r->rtt_rt = rt;
> r->rtt_time = current_time;
> r->rtt_func = func;
> r->rtt_queue = queue;
> r->rtt_tableid = rtableid;
> +
> + mtx_enter(&rttimer_mtx);
> LIST_INSERT_HEAD(&rt->rt_timer, r, rtt_link);
> TAILQ_INSERT_TAIL(&queue->rtq_head, r, rtt_next);
> r->rtt_queue->rtq_count++;
> + mtx_leave(&rttimer_mtx);
>
> return (0);
> }
> @@ -1512,23 +1537,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: /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 20 Apr 2022 18:00:39 -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);
>
--
:wq Claudio