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

Bluetooth: Fix rejected connection not disconnecting ACL link

When using DEFER_SETUP on a RFCOMM socket, a SABM frame triggers
authorization which when rejected send a DM response. This is fine
according to the RFCOMM spec:

the responding implementation may replace the "proper" response
on the Multiplexer Control channel with a DM frame, sent on the
referenced DLCI to indicate that the DLCI is not open, and that
the responder would not grant a request to open it later either.

But some stacks doesn't seems to cope with this leaving DLCI 0 open after
receiving DM frame.

To fix it properly a timer was introduced to rfcomm_session which is used
to set a timeout when the last active DLC of a session is unlinked, this
will give the remote stack some time to reply with a proper DISC frame on
DLCI 0 avoiding both sides sending DISC to each other on stacks that
follow the specification and taking care of those who don't by taking
down DLCI 0.

Signed-off-by: Luiz Augusto von Dentz <luiz.dentz@openbossa.org>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>

authored by

Luiz Augusto von Dentz and committed by
Marcel Holtmann
9e726b17 ef54fd93

+43
+2
include/net/bluetooth/rfcomm.h
··· 29 29 #define RFCOMM_CONN_TIMEOUT (HZ * 30) 30 30 #define RFCOMM_DISC_TIMEOUT (HZ * 20) 31 31 #define RFCOMM_AUTH_TIMEOUT (HZ * 25) 32 + #define RFCOMM_IDLE_TIMEOUT (HZ * 2) 32 33 33 34 #define RFCOMM_DEFAULT_MTU 127 34 35 #define RFCOMM_DEFAULT_CREDITS 7 ··· 155 154 struct rfcomm_session { 156 155 struct list_head list; 157 156 struct socket *sock; 157 + struct timer_list timer; 158 158 unsigned long state; 159 159 unsigned long flags; 160 160 atomic_t refcnt;
+41
net/bluetooth/rfcomm/core.c
··· 244 244 auth_type); 245 245 } 246 246 247 + static void rfcomm_session_timeout(unsigned long arg) 248 + { 249 + struct rfcomm_session *s = (void *) arg; 250 + 251 + BT_DBG("session %p state %ld", s, s->state); 252 + 253 + set_bit(RFCOMM_TIMED_OUT, &s->flags); 254 + rfcomm_session_put(s); 255 + rfcomm_schedule(RFCOMM_SCHED_TIMEO); 256 + } 257 + 258 + static void rfcomm_session_set_timer(struct rfcomm_session *s, long timeout) 259 + { 260 + BT_DBG("session %p state %ld timeout %ld", s, s->state, timeout); 261 + 262 + if (!mod_timer(&s->timer, jiffies + timeout)) 263 + rfcomm_session_hold(s); 264 + } 265 + 266 + static void rfcomm_session_clear_timer(struct rfcomm_session *s) 267 + { 268 + BT_DBG("session %p state %ld", s, s->state); 269 + 270 + if (timer_pending(&s->timer) && del_timer(&s->timer)) 271 + rfcomm_session_put(s); 272 + } 273 + 247 274 /* ---- RFCOMM DLCs ---- */ 248 275 static void rfcomm_dlc_timeout(unsigned long arg) 249 276 { ··· 347 320 348 321 rfcomm_session_hold(s); 349 322 323 + rfcomm_session_clear_timer(s); 350 324 rfcomm_dlc_hold(d); 351 325 list_add(&d->list, &s->dlcs); 352 326 d->session = s; ··· 362 334 list_del(&d->list); 363 335 d->session = NULL; 364 336 rfcomm_dlc_put(d); 337 + 338 + if (list_empty(&s->dlcs)) 339 + rfcomm_session_set_timer(s, RFCOMM_IDLE_TIMEOUT); 365 340 366 341 rfcomm_session_put(s); 367 342 } ··· 598 567 599 568 BT_DBG("session %p sock %p", s, sock); 600 569 570 + setup_timer(&s->timer, rfcomm_session_timeout, (unsigned long) s); 571 + 601 572 INIT_LIST_HEAD(&s->dlcs); 602 573 s->state = state; 603 574 s->sock = sock; ··· 631 598 if (state == BT_CONNECTED) 632 599 rfcomm_send_disc(s, 0); 633 600 601 + rfcomm_session_clear_timer(s); 634 602 sock_release(s->sock); 635 603 kfree(s); 636 604 ··· 673 639 __rfcomm_dlc_close(d, err); 674 640 } 675 641 642 + rfcomm_session_clear_timer(s); 676 643 rfcomm_session_put(s); 677 644 } 678 645 ··· 1913 1878 list_for_each_safe(p, n, &session_list) { 1914 1879 struct rfcomm_session *s; 1915 1880 s = list_entry(p, struct rfcomm_session, list); 1881 + 1882 + if (test_and_clear_bit(RFCOMM_TIMED_OUT, &s->flags)) { 1883 + s->state = BT_DISCONN; 1884 + rfcomm_send_disc(s, 0); 1885 + continue; 1886 + } 1916 1887 1917 1888 if (s->state == BT_LISTEN) { 1918 1889 rfcomm_accept_connection(s);