diff options
author | Andy Grover <agrover@redhat.com> | 2014-01-24 19:18:54 -0500 |
---|---|---|
committer | Nicholas Bellinger <nab@linux-iscsi.org> | 2014-01-30 06:57:59 -0500 |
commit | ee291e63293146db64668e8d65eb35c97e8324f4 (patch) | |
tree | 181aa6e882c51aa5ccfe0d116a51a3552e2055f8 | |
parent | 76736db3e291246fbce9db856706af3454b0b078 (diff) |
target/iscsi: Fix network portal creation race
When creating network portals rapidly, such as when restoring a
configuration, LIO's code to reuse existing portals can return a false
negative if the thread hasn't run yet and set np_thread_state to
ISCSI_NP_THREAD_ACTIVE. This causes an error in the network stack
when attempting to bind to the same address/port.
This patch sets NP_THREAD_ACTIVE before the np is placed on g_np_list,
so even if the thread hasn't run yet, iscsit_get_np will return the
existing np.
Also, convert np_lock -> np_mutex + hold across adding new net portal
to g_np_list to prevent a race where two threads may attempt to create
the same network portal, resulting in one of them failing.
(nab: Add missing mutex_unlocks in iscsit_add_np failure paths)
(DanC: Fix incorrect spin_unlock -> spin_unlock_bh)
Signed-off-by: Andy Grover <agrover@redhat.com>
Cc: <stable@vger.kernel.org> #3.1+
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
-rw-r--r-- | drivers/target/iscsi/iscsi_target.c | 34 |
1 files changed, 21 insertions, 13 deletions
diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 2a52752a9937..a99637e9e820 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c | |||
@@ -52,7 +52,7 @@ | |||
52 | static LIST_HEAD(g_tiqn_list); | 52 | static LIST_HEAD(g_tiqn_list); |
53 | static LIST_HEAD(g_np_list); | 53 | static LIST_HEAD(g_np_list); |
54 | static DEFINE_SPINLOCK(tiqn_lock); | 54 | static DEFINE_SPINLOCK(tiqn_lock); |
55 | static DEFINE_SPINLOCK(np_lock); | 55 | static DEFINE_MUTEX(np_lock); |
56 | 56 | ||
57 | static struct idr tiqn_idr; | 57 | static struct idr tiqn_idr; |
58 | struct idr sess_idr; | 58 | struct idr sess_idr; |
@@ -307,6 +307,9 @@ bool iscsit_check_np_match( | |||
307 | return false; | 307 | return false; |
308 | } | 308 | } |
309 | 309 | ||
310 | /* | ||
311 | * Called with mutex np_lock held | ||
312 | */ | ||
310 | static struct iscsi_np *iscsit_get_np( | 313 | static struct iscsi_np *iscsit_get_np( |
311 | struct __kernel_sockaddr_storage *sockaddr, | 314 | struct __kernel_sockaddr_storage *sockaddr, |
312 | int network_transport) | 315 | int network_transport) |
@@ -314,11 +317,10 @@ static struct iscsi_np *iscsit_get_np( | |||
314 | struct iscsi_np *np; | 317 | struct iscsi_np *np; |
315 | bool match; | 318 | bool match; |
316 | 319 | ||
317 | spin_lock_bh(&np_lock); | ||
318 | list_for_each_entry(np, &g_np_list, np_list) { | 320 | list_for_each_entry(np, &g_np_list, np_list) { |
319 | spin_lock(&np->np_thread_lock); | 321 | spin_lock_bh(&np->np_thread_lock); |
320 | if (np->np_thread_state != ISCSI_NP_THREAD_ACTIVE) { | 322 | if (np->np_thread_state != ISCSI_NP_THREAD_ACTIVE) { |
321 | spin_unlock(&np->np_thread_lock); | 323 | spin_unlock_bh(&np->np_thread_lock); |
322 | continue; | 324 | continue; |
323 | } | 325 | } |
324 | 326 | ||
@@ -330,13 +332,11 @@ static struct iscsi_np *iscsit_get_np( | |||
330 | * while iscsi_tpg_add_network_portal() is called. | 332 | * while iscsi_tpg_add_network_portal() is called. |
331 | */ | 333 | */ |
332 | np->np_exports++; | 334 | np->np_exports++; |
333 | spin_unlock(&np->np_thread_lock); | 335 | spin_unlock_bh(&np->np_thread_lock); |
334 | spin_unlock_bh(&np_lock); | ||
335 | return np; | 336 | return np; |
336 | } | 337 | } |
337 | spin_unlock(&np->np_thread_lock); | 338 | spin_unlock_bh(&np->np_thread_lock); |
338 | } | 339 | } |
339 | spin_unlock_bh(&np_lock); | ||
340 | 340 | ||
341 | return NULL; | 341 | return NULL; |
342 | } | 342 | } |
@@ -350,16 +350,22 @@ struct iscsi_np *iscsit_add_np( | |||
350 | struct sockaddr_in6 *sock_in6; | 350 | struct sockaddr_in6 *sock_in6; |
351 | struct iscsi_np *np; | 351 | struct iscsi_np *np; |
352 | int ret; | 352 | int ret; |
353 | |||
354 | mutex_lock(&np_lock); | ||
355 | |||
353 | /* | 356 | /* |
354 | * Locate the existing struct iscsi_np if already active.. | 357 | * Locate the existing struct iscsi_np if already active.. |
355 | */ | 358 | */ |
356 | np = iscsit_get_np(sockaddr, network_transport); | 359 | np = iscsit_get_np(sockaddr, network_transport); |
357 | if (np) | 360 | if (np) { |
361 | mutex_unlock(&np_lock); | ||
358 | return np; | 362 | return np; |
363 | } | ||
359 | 364 | ||
360 | np = kzalloc(sizeof(struct iscsi_np), GFP_KERNEL); | 365 | np = kzalloc(sizeof(struct iscsi_np), GFP_KERNEL); |
361 | if (!np) { | 366 | if (!np) { |
362 | pr_err("Unable to allocate memory for struct iscsi_np\n"); | 367 | pr_err("Unable to allocate memory for struct iscsi_np\n"); |
368 | mutex_unlock(&np_lock); | ||
363 | return ERR_PTR(-ENOMEM); | 369 | return ERR_PTR(-ENOMEM); |
364 | } | 370 | } |
365 | 371 | ||
@@ -382,6 +388,7 @@ struct iscsi_np *iscsit_add_np( | |||
382 | ret = iscsi_target_setup_login_socket(np, sockaddr); | 388 | ret = iscsi_target_setup_login_socket(np, sockaddr); |
383 | if (ret != 0) { | 389 | if (ret != 0) { |
384 | kfree(np); | 390 | kfree(np); |
391 | mutex_unlock(&np_lock); | ||
385 | return ERR_PTR(ret); | 392 | return ERR_PTR(ret); |
386 | } | 393 | } |
387 | 394 | ||
@@ -390,6 +397,7 @@ struct iscsi_np *iscsit_add_np( | |||
390 | pr_err("Unable to create kthread: iscsi_np\n"); | 397 | pr_err("Unable to create kthread: iscsi_np\n"); |
391 | ret = PTR_ERR(np->np_thread); | 398 | ret = PTR_ERR(np->np_thread); |
392 | kfree(np); | 399 | kfree(np); |
400 | mutex_unlock(&np_lock); | ||
393 | return ERR_PTR(ret); | 401 | return ERR_PTR(ret); |
394 | } | 402 | } |
395 | /* | 403 | /* |
@@ -400,10 +408,10 @@ struct iscsi_np *iscsit_add_np( | |||
400 | * point because iscsi_np has not been added to g_np_list yet. | 408 | * point because iscsi_np has not been added to g_np_list yet. |
401 | */ | 409 | */ |
402 | np->np_exports = 1; | 410 | np->np_exports = 1; |
411 | np->np_thread_state = ISCSI_NP_THREAD_ACTIVE; | ||
403 | 412 | ||
404 | spin_lock_bh(&np_lock); | ||
405 | list_add_tail(&np->np_list, &g_np_list); | 413 | list_add_tail(&np->np_list, &g_np_list); |
406 | spin_unlock_bh(&np_lock); | 414 | mutex_unlock(&np_lock); |
407 | 415 | ||
408 | pr_debug("CORE[0] - Added Network Portal: %s:%hu on %s\n", | 416 | pr_debug("CORE[0] - Added Network Portal: %s:%hu on %s\n", |
409 | np->np_ip, np->np_port, np->np_transport->name); | 417 | np->np_ip, np->np_port, np->np_transport->name); |
@@ -469,9 +477,9 @@ int iscsit_del_np(struct iscsi_np *np) | |||
469 | 477 | ||
470 | np->np_transport->iscsit_free_np(np); | 478 | np->np_transport->iscsit_free_np(np); |
471 | 479 | ||
472 | spin_lock_bh(&np_lock); | 480 | mutex_lock(&np_lock); |
473 | list_del(&np->np_list); | 481 | list_del(&np->np_list); |
474 | spin_unlock_bh(&np_lock); | 482 | mutex_unlock(&np_lock); |
475 | 483 | ||
476 | pr_debug("CORE[0] - Removed Network Portal: %s:%hu on %s\n", | 484 | pr_debug("CORE[0] - Removed Network Portal: %s:%hu on %s\n", |
477 | np->np_ip, np->np_port, np->np_transport->name); | 485 | np->np_ip, np->np_port, np->np_transport->name); |