Merge "chan_iax2: Prevent deadlock between hangup and sending lagrq/ping"
authorMark Michelson <mmichelson@digium.com>
Wed, 10 Jun 2015 17:06:02 +0000 (12:06 -0500)
committerGerrit Code Review <gerrit2@gerrit.digium.api>
Wed, 10 Jun 2015 17:06:02 +0000 (12:06 -0500)
1  2 
channels/chan_iax2.c

diff --combined channels/chan_iax2.c
@@@ -395,8 -395,6 +395,6 @@@ static int (*iax2_regfunk)(const char *
  static struct io_context *io;
  static struct ast_sched_context *sched;
  
- #define DONT_RESCHEDULE -2
  static iax2_format iax2_capability = IAX_CAPABILITY_FULLBANDWIDTH;
  
  static int iaxdebug = 0;
@@@ -435,37 -433,37 +433,37 @@@ struct iax2_context 
  };
  
  
 -#define       IAX_HASCALLERID         (uint64_t)(1 << 0)    /*!< CallerID has been specified */
 -#define IAX_DELME               (uint64_t)(1 << 1)    /*!< Needs to be deleted */
 -#define IAX_TEMPONLY            (uint64_t)(1 << 2)    /*!< Temporary (realtime) */
 -#define IAX_TRUNK               (uint64_t)(1 << 3)    /*!< Treat as a trunk */
 -#define IAX_NOTRANSFER          (uint64_t)(1 << 4)    /*!< Don't native bridge */
 -#define IAX_USEJITTERBUF        (uint64_t)(1 << 5)    /*!< Use jitter buffer */
 -#define IAX_DYNAMIC             (uint64_t)(1 << 6)    /*!< dynamic peer */
 -#define IAX_SENDANI             (uint64_t)(1 << 7)    /*!< Send ANI along with CallerID */
 -#define IAX_RTSAVE_SYSNAME      (uint64_t)(1 << 8)    /*!< Save Systname on Realtime Updates */
 -#define IAX_ALREADYGONE         (uint64_t)(1 << 9)    /*!< Already disconnected */
 -#define IAX_PROVISION           (uint64_t)(1 << 10)   /*!< This is a provisioning request */
 -#define IAX_QUELCH              (uint64_t)(1 << 11)   /*!< Whether or not we quelch audio */
 -#define IAX_ENCRYPTED           (uint64_t)(1 << 12)   /*!< Whether we should assume encrypted tx/rx */
 -#define IAX_KEYPOPULATED        (uint64_t)(1 << 13)   /*!< Whether we have a key populated */
 -#define IAX_CODEC_USER_FIRST    (uint64_t)(1 << 14)   /*!< are we willing to let the other guy choose the codec? */
 -#define IAX_CODEC_NOPREFS       (uint64_t)(1 << 15)   /*!< Force old behaviour by turning off prefs */
 -#define IAX_CODEC_NOCAP         (uint64_t)(1 << 16)   /*!< only consider requested format and ignore capabilities*/
 -#define IAX_RTCACHEFRIENDS      (uint64_t)(1 << 17)   /*!< let realtime stay till your reload */
 -#define IAX_RTUPDATE            (uint64_t)(1 << 18)   /*!< Send a realtime update */
 -#define IAX_RTAUTOCLEAR         (uint64_t)(1 << 19)   /*!< erase me on expire */
 -#define IAX_RTIGNOREREGEXPIRE   (uint64_t)(1 << 21)   /*!< When using realtime, ignore registration expiration */
 -#define IAX_TRUNKTIMESTAMPS     (uint64_t)(1 << 22)   /*!< Send trunk timestamps */
 -#define IAX_TRANSFERMEDIA       (uint64_t)(1 << 23)   /*!< When doing IAX2 transfers, transfer media only */
 -#define IAX_MAXAUTHREQ          (uint64_t)(1 << 24)   /*!< Maximum outstanding AUTHREQ restriction is in place */
 -#define IAX_DELAYPBXSTART       (uint64_t)(1 << 25)   /*!< Don't start a PBX on the channel until the peer sends us a response, so that we've achieved a three-way handshake with them before sending voice or anything else */
 -#define IAX_ALLOWFWDOWNLOAD     (uint64_t)(1 << 26)   /*!< Allow the FWDOWNL command? */
 -#define IAX_IMMEDIATE           (uint64_t)(1 << 27)   /*!< Allow immediate off-hook to extension s */
 -#define IAX_SENDCONNECTEDLINE   (uint64_t)(1 << 28)   /*!< Allow sending of connected line updates */
 -#define IAX_RECVCONNECTEDLINE   (uint64_t)(1 << 29)   /*!< Allow receiving of connected line updates */
 -#define IAX_FORCE_ENCRYPT       (uint64_t)(1 << 30)   /*!< Forces call encryption, if encryption not possible hangup */
 -#define IAX_SHRINKCALLERID      (uint64_t)(1 << 31)   /*!< Turn on and off caller id shrinking */
 +#define IAX_HASCALLERID         (uint64_t)(1LLU << 0)    /*!< CallerID has been specified */
 +#define IAX_DELME               (uint64_t)(1LLU << 1)    /*!< Needs to be deleted */
 +#define IAX_TEMPONLY            (uint64_t)(1LLU << 2)    /*!< Temporary (realtime) */
 +#define IAX_TRUNK               (uint64_t)(1LLU << 3)    /*!< Treat as a trunk */
 +#define IAX_NOTRANSFER          (uint64_t)(1LLU << 4)    /*!< Don't native bridge */
 +#define IAX_USEJITTERBUF        (uint64_t)(1LLU << 5)    /*!< Use jitter buffer */
 +#define IAX_DYNAMIC             (uint64_t)(1LLU << 6)    /*!< dynamic peer */
 +#define IAX_SENDANI             (uint64_t)(1LLU << 7)    /*!< Send ANI along with CallerID */
 +#define IAX_RTSAVE_SYSNAME      (uint64_t)(1LLU << 8)    /*!< Save Systname on Realtime Updates */
 +#define IAX_ALREADYGONE         (uint64_t)(1LLU << 9)    /*!< Already disconnected */
 +#define IAX_PROVISION           (uint64_t)(1LLU << 10)   /*!< This is a provisioning request */
 +#define IAX_QUELCH              (uint64_t)(1LLU << 11)   /*!< Whether or not we quelch audio */
 +#define IAX_ENCRYPTED           (uint64_t)(1LLU << 12)   /*!< Whether we should assume encrypted tx/rx */
 +#define IAX_KEYPOPULATED        (uint64_t)(1LLU << 13)   /*!< Whether we have a key populated */
 +#define IAX_CODEC_USER_FIRST    (uint64_t)(1LLU << 14)   /*!< are we willing to let the other guy choose the codec? */
 +#define IAX_CODEC_NOPREFS       (uint64_t)(1LLU << 15)   /*!< Force old behaviour by turning off prefs */
 +#define IAX_CODEC_NOCAP         (uint64_t)(1LLU << 16)   /*!< only consider requested format and ignore capabilities*/
 +#define IAX_RTCACHEFRIENDS      (uint64_t)(1LLU << 17)   /*!< let realtime stay till your reload */
 +#define IAX_RTUPDATE            (uint64_t)(1LLU << 18)   /*!< Send a realtime update */
 +#define IAX_RTAUTOCLEAR         (uint64_t)(1LLU << 19)   /*!< erase me on expire */
 +#define IAX_RTIGNOREREGEXPIRE   (uint64_t)(1LLU << 21)   /*!< When using realtime, ignore registration expiration */
 +#define IAX_TRUNKTIMESTAMPS     (uint64_t)(1LLU << 22)   /*!< Send trunk timestamps */
 +#define IAX_TRANSFERMEDIA       (uint64_t)(1LLU << 23)   /*!< When doing IAX2 transfers, transfer media only */
 +#define IAX_MAXAUTHREQ          (uint64_t)(1LLU << 24)   /*!< Maximum outstanding AUTHREQ restriction is in place */
 +#define IAX_DELAYPBXSTART       (uint64_t)(1LLU << 25)   /*!< Don't start a PBX on the channel until the peer sends us a response, so that we've achieved a three-way handshake with them before sending voice or anything else */
 +#define IAX_ALLOWFWDOWNLOAD     (uint64_t)(1LLU << 26)   /*!< Allow the FWDOWNL command? */
 +#define IAX_IMMEDIATE           (uint64_t)(1LLU << 27)   /*!< Allow immediate off-hook to extension s */
 +#define IAX_SENDCONNECTEDLINE   (uint64_t)(1LLU << 28)   /*!< Allow sending of connected line updates */
 +#define IAX_RECVCONNECTEDLINE   (uint64_t)(1LLU << 29)   /*!< Allow receiving of connected line updates */
 +#define IAX_FORCE_ENCRYPT       (uint64_t)(1LLU << 30)   /*!< Forces call encryption, if encryption not possible hangup */
 +#define IAX_SHRINKCALLERID      (uint64_t)(1LLU << 31)   /*!< Turn on and off caller id shrinking */
  static int global_rtautoclear = 120;
  
  static int reload_config(int forced_reload);
@@@ -864,6 -862,8 +862,8 @@@ struct chan_iax2_pvt 
        int frames_dropped;
        /*! received frame count: (just for stats) */
        int frames_received;
+       /*! Destroying this call initiated. */
+       int destroy_initiated;
        /*! num bytes used for calltoken ie, even an empty ie should contain 2 */
        unsigned char calltoken_ie_len;
        /*! hold all signaling frames from the pbx thread until we have a destination callno */
@@@ -1430,6 -1430,13 +1430,6 @@@ static int iax2_is_control_frame_allowe
        return is_allowed;
  }
  
 -static void mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg)
 -{
 -      /* The MWI subscriptions exist just so the core knows we care about those
 -       * mailboxes.  However, we just grab the events out of the cache when it
 -       * is time to send MWI, since it is only sent with a REGACK. */
 -}
 -
  static void network_change_stasis_subscribe(void)
  {
        if (!network_change_sub) {
@@@ -1664,23 -1671,48 +1664,48 @@@ static int iax2_sched_add(struct ast_sc
        return ast_sched_add(con, when, callback, data);
  }
  
+ /*
+  * \brief Acquire the iaxsl[callno] if call exists and not having ongoing hangup.
+  * \param callno Call number to lock.
+  * \return 0 If call disappeared or has ongoing hangup procedure. 1 If call found and mutex is locked.
+  */
+ static int iax2_lock_callno_unless_destroyed(int callno)
+ {
+       ast_mutex_lock(&iaxsl[callno]);
+       /* We acquired the lock; but the call was already destroyed (we came after full hang up procedures)
+        * or destroy initiated (in middle of hang up procedure. */
+       if (!iaxs[callno] || iaxs[callno]->destroy_initiated) {
+               ast_debug(3, "I wanted to lock callno %d, but it is dead or going to die.\n", callno);
+               ast_mutex_unlock(&iaxsl[callno]);
+               return 0;
+       }
+       /* Lock acquired, and callno is alive and kicking. */
+       return 1;
+ }
  static int send_ping(const void *data);
  
  static void __send_ping(const void *data)
  {
-       int callno = (long) data;
+       int callno = PTR_TO_CALLNO(data);
  
-       ast_mutex_lock(&iaxsl[callno]);
+       if (iax2_lock_callno_unless_destroyed(callno) == 0) {
+               ast_debug(3, "Hangup initiated on call %d, aborting __send_ping\n", callno);
+               return;
+       }
  
-       if (iaxs[callno]) {
-               if (iaxs[callno]->peercallno) {
-                       send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_PING, 0, NULL, 0, -1);
-                       if (iaxs[callno]->pingid != DONT_RESCHEDULE) {
-                               iaxs[callno]->pingid = iax2_sched_add(sched, ping_time * 1000, send_ping, data);
-                       }
-               }
-       } else {
-               ast_debug(1, "I was supposed to send a PING with callno %d, but no such call exists.\n", callno);
+       /* Mark pingid as invalid scheduler id. */
+       iaxs[callno]->pingid = -1;
+       /* callno is now locked. */
+       if (iaxs[callno]->peercallno) {
+               /* Send PING packet. */
+               send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_PING, 0, NULL, 0, -1);
+               /* Schedule sending next ping. */
+               iaxs[callno]->pingid = iax2_sched_add(sched, ping_time * 1000, send_ping, data);
        }
  
        ast_mutex_unlock(&iaxsl[callno]);
  
  static int send_ping(const void *data)
  {
-       int callno = (long) data;
-       ast_mutex_lock(&iaxsl[callno]);
-       if (iaxs[callno] && iaxs[callno]->pingid != DONT_RESCHEDULE) {
-               iaxs[callno]->pingid = -1;
-       }
-       ast_mutex_unlock(&iaxsl[callno]);
  #ifdef SCHED_MULTITHREADED
        if (schedule_action(__send_ping, data))
  #endif
@@@ -1735,19 -1760,23 +1753,23 @@@ static int send_lagrq(const void *data)
  
  static void __send_lagrq(const void *data)
  {
-       int callno = (long) data;
+       int callno = PTR_TO_CALLNO(data);
  
-       ast_mutex_lock(&iaxsl[callno]);
+       if (iax2_lock_callno_unless_destroyed(callno) == 0) {
+               ast_debug(3, "Hangup initiated on call %d, aborting __send_lagrq\n", callno);
+               return;
+       }
  
-       if (iaxs[callno]) {
-               if (iaxs[callno]->peercallno) {
-                       send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_LAGRQ, 0, NULL, 0, -1);
-                       if (iaxs[callno]->lagid != DONT_RESCHEDULE) {
-                               iaxs[callno]->lagid = iax2_sched_add(sched, lagrq_time * 1000, send_lagrq, data);
-                       }
-               }
-       } else {
-               ast_debug(1, "I was supposed to send a LAGRQ with callno %d, but no such call exists.\n", callno);
+       /* Mark lagid as invalid scheduler id. */
+       iaxs[callno]->lagid = -1;
+       /* callno is now locked. */
+       if (iaxs[callno]->peercallno) {
+               /* Send LAGRQ packet. */
+               send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_LAGRQ, 0, NULL, 0, -1);
+               /* Schedule sending next lagrq. */
+               iaxs[callno]->lagid = iax2_sched_add(sched, lagrq_time * 1000, send_lagrq, data);
        }
  
        ast_mutex_unlock(&iaxsl[callno]);
  
  static int send_lagrq(const void *data)
  {
-       int callno = (long) data;
-       ast_mutex_lock(&iaxsl[callno]);
-       if (iaxs[callno] && iaxs[callno]->lagid != DONT_RESCHEDULE) {
-               iaxs[callno]->lagid = -1;
-       }
-       ast_mutex_unlock(&iaxsl[callno]);
  #ifdef SCHED_MULTITHREADED
        if (schedule_action(__send_lagrq, data))
  #endif
@@@ -2045,6 -2067,16 +2060,16 @@@ static int iax2_getpeername(struct ast_
        return res;
  }
  
+ /* Call AST_SCHED_DEL on a scheduled task if it is found in scheduler. */
+ static int iax2_delete_from_sched(const void* data)
+ {
+       int sched_id = (int)(long)data;
+       AST_SCHED_DEL(sched, sched_id);
+       return 0;
+ }
  /*!\note Assumes the lock on the pvt is already held, when
   * iax2_destroy_helper() is called. */
  static void iax2_destroy_helper(struct chan_iax2_pvt *pvt)
  
                ast_clear_flag64(pvt, IAX_MAXAUTHREQ);
        }
-       /* No more pings or lagrq's */
-       AST_SCHED_DEL_SPINLOCK(sched, pvt->pingid, &iaxsl[pvt->callno]);
-       pvt->pingid = DONT_RESCHEDULE;
-       AST_SCHED_DEL_SPINLOCK(sched, pvt->lagid, &iaxsl[pvt->callno]);
-       pvt->lagid = DONT_RESCHEDULE;
+       /* Mark call destroy initiated flag. */
+       pvt->destroy_initiated = 1;
+       /*
+        * Schedule deleting the scheduled (but didn't run yet) PINGs or LAGRQs.
+        * Already running tasks will be terminated because of destroy_initiated.
+        *
+        * Don't call AST_SCHED_DEL from this thread for pingid and lagid because
+        * it leads to a deadlock between the scheduler thread callback locking
+        * the callno mutex and this thread which holds the callno mutex one or
+        * more times.  It is better to have another thread delete the scheduled
+        * callbacks which doesn't lock the callno mutex.
+        */
+       iax2_sched_add(sched, 0, iax2_delete_from_sched, (void*)(long)pvt->pingid);
+       iax2_sched_add(sched, 0, iax2_delete_from_sched, (void*)(long)pvt->lagid);
+       pvt->pingid = -1;
+       pvt->lagid = -1;
        AST_SCHED_DEL(sched, pvt->autoid);
        AST_SCHED_DEL(sched, pvt->authid);
        AST_SCHED_DEL(sched, pvt->initid);
@@@ -13003,10 -13051,7 +13044,10 @@@ static struct iax2_peer *build_peer(con
  
                mailbox_specific_topic = ast_mwi_topic(peer->mailbox);
                if (mailbox_specific_topic) {
 -                      peer->mwi_event_sub = stasis_subscribe_pool(mailbox_specific_topic, mwi_event_cb, NULL);
 +                      /* The MWI subscriptions exist just so the core knows we care about those
 +                       * mailboxes.  However, we just grab the events out of the cache when it
 +                       * is time to send MWI, since it is only sent with a REGACK. */
 +                      peer->mwi_event_sub = stasis_subscribe_pool(mailbox_specific_topic, stasis_subscription_cb_noop, NULL);
                }
        }
  
@@@ -13742,7 -13787,6 +13783,7 @@@ static int set_config(const char *confi
                } else if (!strcasecmp(v->name, "calltokenoptional")) {
                        if (add_calltoken_ignore(v->value)) {
                                ast_log(LOG_WARNING, "Invalid calltokenoptional address range - '%s' line %d\n", v->value, v->lineno);
 +                              return -1;
                        }
                } else if (!strcasecmp(v->name, "calltokenexpiration")) {
                        int temp = -1;
@@@ -14626,6 -14670,7 +14667,6 @@@ static void cleanup_thread_list(void *h
  
  static int __unload_module(void)
  {
 -      struct ast_context *con;
        int x;
  
        network_change_stasis_unsubscribe();
        sched = NULL;
        ao2_ref(peercnts, -1);
  
 -      con = ast_context_find(regcontext);
 -      if (con)
 -              ast_context_destroy(con, "IAX2");
 +      ast_context_destroy_by_name(regcontext, "IAX2");
        ast_unload_realtime("iaxpeers");
  
        ao2_ref(iax2_tech.capabilities, -1);
@@@ -15143,10 -15190,10 +15184,10 @@@ static int load_module(void
  }
  
  AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Inter Asterisk eXchange (Ver 2)",
 -              .support_level = AST_MODULE_SUPPORT_CORE,
 -              .load = load_module,
 -              .unload = unload_module,
 -              .reload = reload,
 -              .load_pri = AST_MODPRI_CHANNEL_DRIVER,
 -              .nonoptreq = "res_crypto",
 -              );
 +      .support_level = AST_MODULE_SUPPORT_CORE,
 +      .load = load_module,
 +      .unload = unload_module,
 +      .reload = reload,
 +      .load_pri = AST_MODPRI_CHANNEL_DRIVER,
 +      .nonoptreq = "res_crypto",
 +);