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?
bluhm
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);