Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

batman-adv: Drop unmanaged ELP metric worker

The ELP worker needs to calculate new metric values for all neighbors
"reachable" over an interface. Some of the used metric sources require
locks which might need to sleep. This sleep is incompatible with the RCU
list iterator used for the recorded neighbors. The initial approach to work
around of this problem was to queue another work item per neighbor and then
run this in a new context.

Even when this solved the RCU vs might_sleep() conflict, it has a major
problems: Nothing was stopping the work item in case it is not needed
anymore - for example because one of the related interfaces was removed or
the batman-adv module was unloaded - resulting in potential invalid memory
accesses.

Directly canceling the metric worker also has various problems:

* cancel_work_sync for a to-be-deactivated interface is called with
rtnl_lock held. But the code in the ELP metric worker also tries to use
rtnl_lock() - which will never return in this case. This also means that
cancel_work_sync would never return because it is waiting for the worker
to finish.
* iterating over the neighbor list for the to-be-deactivated interface is
currently done using the RCU specific methods. Which means that it is
possible to miss items when iterating over it without the associated
spinlock - a behaviour which is acceptable for a periodic metric check
but not for a cleanup routine (which must "stop" all still running
workers)

The better approch is to get rid of the per interface neighbor metric
worker and handle everything in the interface worker. The original problems
are solved by:

* creating a list of neighbors which require new metric information inside
the RCU protected context, gathering the metric according to the new list
outside the RCU protected context
* only use rcu_trylock inside metric gathering code to avoid a deadlock
when the cancel_delayed_work_sync is called in the interface removal code
(which is called with the rtnl_lock held)

Cc: stable@vger.kernel.org
Fixes: c833484e5f38 ("batman-adv: ELP - compute the metric based on the estimated throughput")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>

authored by

Sven Eckelmann and committed by
Simon Wunderlich
8c8ecc98 e7e34ffc

+48 -30
-2
net/batman-adv/bat_v.c
··· 113 113 batadv_v_hardif_neigh_init(struct batadv_hardif_neigh_node *hardif_neigh) 114 114 { 115 115 ewma_throughput_init(&hardif_neigh->bat_v.throughput); 116 - INIT_WORK(&hardif_neigh->bat_v.metric_work, 117 - batadv_v_elp_throughput_metric_update); 118 116 } 119 117 120 118 /**
+48 -23
net/batman-adv/bat_v_elp.c
··· 18 18 #include <linux/if_ether.h> 19 19 #include <linux/jiffies.h> 20 20 #include <linux/kref.h> 21 + #include <linux/list.h> 21 22 #include <linux/minmax.h> 22 23 #include <linux/netdevice.h> 23 24 #include <linux/nl80211.h> ··· 27 26 #include <linux/rcupdate.h> 28 27 #include <linux/rtnetlink.h> 29 28 #include <linux/skbuff.h> 29 + #include <linux/slab.h> 30 30 #include <linux/stddef.h> 31 31 #include <linux/string.h> 32 32 #include <linux/types.h> ··· 42 40 #include "originator.h" 43 41 #include "routing.h" 44 42 #include "send.h" 43 + 44 + /** 45 + * struct batadv_v_metric_queue_entry - list of hardif neighbors which require 46 + * and metric update 47 + */ 48 + struct batadv_v_metric_queue_entry { 49 + /** @hardif_neigh: hardif neighbor scheduled for metric update */ 50 + struct batadv_hardif_neigh_node *hardif_neigh; 51 + 52 + /** @list: list node for metric_queue */ 53 + struct list_head list; 54 + }; 45 55 46 56 /** 47 57 * batadv_v_elp_start_timer() - restart timer for ELP periodic work ··· 151 137 goto default_throughput; 152 138 } 153 139 140 + /* only use rtnl_trylock because the elp worker will be cancelled while 141 + * the rntl_lock is held. the cancel_delayed_work_sync() would otherwise 142 + * wait forever when the elp work_item was started and it is then also 143 + * trying to rtnl_lock 144 + */ 145 + if (!rtnl_trylock()) 146 + return false; 147 + 154 148 /* if not a wifi interface, check if this device provides data via 155 149 * ethtool (e.g. an Ethernet adapter) 156 150 */ 157 - rtnl_lock(); 158 151 ret = __ethtool_get_link_ksettings(hard_iface->net_dev, &link_settings); 159 152 rtnl_unlock(); 160 153 if (ret == 0) { ··· 196 175 /** 197 176 * batadv_v_elp_throughput_metric_update() - worker updating the throughput 198 177 * metric of a single hop neighbour 199 - * @work: the work queue item 178 + * @neigh: the neighbour to probe 200 179 */ 201 - void batadv_v_elp_throughput_metric_update(struct work_struct *work) 180 + static void 181 + batadv_v_elp_throughput_metric_update(struct batadv_hardif_neigh_node *neigh) 202 182 { 203 - struct batadv_hardif_neigh_node_bat_v *neigh_bat_v; 204 - struct batadv_hardif_neigh_node *neigh; 205 183 u32 throughput; 206 184 bool valid; 207 185 208 - neigh_bat_v = container_of(work, struct batadv_hardif_neigh_node_bat_v, 209 - metric_work); 210 - neigh = container_of(neigh_bat_v, struct batadv_hardif_neigh_node, 211 - bat_v); 212 - 213 186 valid = batadv_v_elp_get_throughput(neigh, &throughput); 214 187 if (!valid) 215 - goto put_neigh; 188 + return; 216 189 217 190 ewma_throughput_add(&neigh->bat_v.throughput, throughput); 218 - 219 - put_neigh: 220 - /* decrement refcounter to balance increment performed before scheduling 221 - * this task 222 - */ 223 - batadv_hardif_neigh_put(neigh); 224 191 } 225 192 226 193 /** ··· 282 273 */ 283 274 static void batadv_v_elp_periodic_work(struct work_struct *work) 284 275 { 276 + struct batadv_v_metric_queue_entry *metric_entry; 277 + struct batadv_v_metric_queue_entry *metric_safe; 285 278 struct batadv_hardif_neigh_node *hardif_neigh; 286 279 struct batadv_hard_iface *hard_iface; 287 280 struct batadv_hard_iface_bat_v *bat_v; 288 281 struct batadv_elp_packet *elp_packet; 282 + struct list_head metric_queue; 289 283 struct batadv_priv *bat_priv; 290 284 struct sk_buff *skb; 291 285 u32 elp_interval; 292 - bool ret; 293 286 294 287 bat_v = container_of(work, struct batadv_hard_iface_bat_v, elp_wq.work); 295 288 hard_iface = container_of(bat_v, struct batadv_hard_iface, bat_v); ··· 327 316 328 317 atomic_inc(&hard_iface->bat_v.elp_seqno); 329 318 319 + INIT_LIST_HEAD(&metric_queue); 320 + 330 321 /* The throughput metric is updated on each sent packet. This way, if a 331 322 * node is dead and no longer sends packets, batman-adv is still able to 332 323 * react timely to its death. ··· 353 340 354 341 /* Reading the estimated throughput from cfg80211 is a task that 355 342 * may sleep and that is not allowed in an rcu protected 356 - * context. Therefore schedule a task for that. 343 + * context. Therefore add it to metric_queue and process it 344 + * outside rcu protected context. 357 345 */ 358 - ret = queue_work(batadv_event_workqueue, 359 - &hardif_neigh->bat_v.metric_work); 360 - 361 - if (!ret) 346 + metric_entry = kzalloc(sizeof(*metric_entry), GFP_ATOMIC); 347 + if (!metric_entry) { 362 348 batadv_hardif_neigh_put(hardif_neigh); 349 + continue; 350 + } 351 + 352 + metric_entry->hardif_neigh = hardif_neigh; 353 + list_add(&metric_entry->list, &metric_queue); 363 354 } 364 355 rcu_read_unlock(); 356 + 357 + list_for_each_entry_safe(metric_entry, metric_safe, &metric_queue, list) { 358 + batadv_v_elp_throughput_metric_update(metric_entry->hardif_neigh); 359 + 360 + batadv_hardif_neigh_put(metric_entry->hardif_neigh); 361 + list_del(&metric_entry->list); 362 + kfree(metric_entry); 363 + } 365 364 366 365 restart_timer: 367 366 batadv_v_elp_start_timer(hard_iface);
-2
net/batman-adv/bat_v_elp.h
··· 10 10 #include "main.h" 11 11 12 12 #include <linux/skbuff.h> 13 - #include <linux/workqueue.h> 14 13 15 14 int batadv_v_elp_iface_enable(struct batadv_hard_iface *hard_iface); 16 15 void batadv_v_elp_iface_disable(struct batadv_hard_iface *hard_iface); ··· 18 19 void batadv_v_elp_primary_iface_set(struct batadv_hard_iface *primary_iface); 19 20 int batadv_v_elp_packet_recv(struct sk_buff *skb, 20 21 struct batadv_hard_iface *if_incoming); 21 - void batadv_v_elp_throughput_metric_update(struct work_struct *work); 22 22 23 23 #endif /* _NET_BATMAN_ADV_BAT_V_ELP_H_ */
-3
net/batman-adv/types.h
··· 596 596 * neighbor 597 597 */ 598 598 unsigned long last_unicast_tx; 599 - 600 - /** @metric_work: work queue callback item for metric update */ 601 - struct work_struct metric_work; 602 599 }; 603 600 604 601 /**