Add protection for SS7 channel allocation and better glare handling.
[asterisk/asterisk.git] / channels / sig_ss7.c
index f1bb75a..616d600 100644 (file)
 #include "asterisk/pbx.h"
 #include "asterisk/causes.h"
 #include "asterisk/musiconhold.h"
+#include "asterisk/cli.h"
 #include "asterisk/transcap.h"
 
 #include "sig_ss7.h"
 
 /* ------------------------------------------------------------------- */
 
+static const char *sig_ss7_call_level2str(enum sig_ss7_call_level level)
+{
+       switch (level) {
+       case SIG_SS7_CALL_LEVEL_IDLE:
+               return "Idle";
+       case SIG_SS7_CALL_LEVEL_ALLOCATED:
+               return "Allocated";
+       case SIG_SS7_CALL_LEVEL_CONTINUITY:
+               return "Continuity";
+       case SIG_SS7_CALL_LEVEL_SETUP:
+               return "Setup";
+       case SIG_SS7_CALL_LEVEL_PROCEEDING:
+               return "Proceeding";
+       case SIG_SS7_CALL_LEVEL_ALERTING:
+               return "Alerting";
+       case SIG_SS7_CALL_LEVEL_CONNECT:
+               return "Connect";
+       case SIG_SS7_CALL_LEVEL_GLARE:
+               return "Glare";
+       }
+       return "Unknown";
+}
+
 #define SIG_SS7_DEADLOCK_AVOIDANCE(p) \
        do { \
                sig_ss7_unlock_private(p); \
@@ -247,7 +271,7 @@ static void sig_ss7_handle_link_exception(struct sig_ss7_linkset *linkset, int w
  * \brief Obtain the sig_ss7 owner channel lock if the owner exists.
  * \since 1.8
  *
- * \param ss7 sig_ss7 SS7 control structure.
+ * \param ss7 SS7 linkset control structure.
  * \param chanpos Channel position in the span.
  *
  * \note Assumes the ss7->lock is already obtained.
@@ -278,7 +302,7 @@ static void sig_ss7_lock_owner(struct sig_ss7_linkset *ss7, int chanpos)
  * \brief Queue the given frame onto the owner channel.
  * \since 1.8
  *
- * \param ss7 sig_ss7 SS7 control structure.
+ * \param ss7 SS7 linkset control structure.
  * \param chanpos Channel position in the span.
  * \param frame Frame to queue onto the owner channel.
  *
@@ -301,7 +325,7 @@ static void sig_ss7_queue_frame(struct sig_ss7_linkset *ss7, int chanpos, struct
  * \brief Queue a control frame of the specified subclass onto the owner channel.
  * \since 1.8
  *
- * \param ss7 sig_ss7 SS7 control structure.
+ * \param ss7 SS7 linkset control structure.
  * \param chanpos Channel position in the span.
  * \param subclass Control frame subclass to queue onto the owner channel.
  *
@@ -323,6 +347,17 @@ static void sig_ss7_queue_control(struct sig_ss7_linkset *ss7, int chanpos, int
        sig_ss7_queue_frame(ss7, chanpos, &f);
 }
 
+/*!
+ * \internal
+ * \brief Find the channel position by CIC/DPC.
+ *
+ * \param linkset SS7 linkset control structure.
+ * \param cic Circuit Identification Code
+ * \param dpc Destination Point Code
+ *
+ * \retval chanpos on success.
+ * \retval -1 on error.
+ */
 static int ss7_find_cic(struct sig_ss7_linkset *linkset, int cic, unsigned int dpc)
 {
        int i;
@@ -336,6 +371,31 @@ static int ss7_find_cic(struct sig_ss7_linkset *linkset, int cic, unsigned int d
        return winner;
 }
 
+/*!
+ * \internal
+ * \brief Find the channel position by CIC/DPC and gripe if not found.
+ *
+ * \param linkset SS7 linkset control structure.
+ * \param cic Circuit Identification Code
+ * \param dpc Destination Point Code
+ * \param msg_name Message type name that failed.
+ *
+ * \retval chanpos on success.
+ * \retval -1 on error.
+ */
+static int ss7_find_cic_gripe(struct sig_ss7_linkset *linkset, int cic, unsigned int dpc, const char *msg_name)
+{
+       int chanpos;
+
+       chanpos = ss7_find_cic(linkset, cic, dpc);
+       if (chanpos < 0) {
+               ast_log(LOG_WARNING, "Linkset %d: SS7 %s requested unconfigured CIC/DPC %d/%d.\n",
+                       linkset->span, msg_name, cic, dpc);
+               return -1;
+       }
+       return chanpos;
+}
+
 static void ss7_handle_cqm(struct sig_ss7_linkset *linkset, int startcic, int endcic, unsigned int dpc)
 {
        unsigned char status[32];
@@ -386,6 +446,7 @@ static inline void ss7_block_cics(struct sig_ss7_linkset *linkset, int startcic,
 {
        int i;
 
+       /* XXX the use of state here seems questionable about matching up with the linkset channels */
        for (i = 0; i < linkset->numchans; i++) {
                if (linkset->pvts[i] && (linkset->pvts[i]->dpc == dpc && ((linkset->pvts[i]->cic >= startcic) && (linkset->pvts[i]->cic <= endcic)))) {
                        if (state) {
@@ -617,8 +678,6 @@ void *ss7_linkset(void *data)
        struct sig_ss7_chan *p;
        int chanpos;
        struct pollfd pollers[SIG_SS7_NUM_DCHANS];
-       int cic;
-       unsigned int dpc;
        int nextms = 0;
 
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
@@ -685,6 +744,11 @@ void *ss7_linkset(void *data)
                }
 
                while ((e = ss7_check_event(ss7))) {
+                       if (linkset->debug) {
+                               ast_verbose("Linkset %d: Processing event: %s\n",
+                                       linkset->span, ss7_event2str(e->e));
+                       }
+
                        switch (e->e) {
                        case SS7_EVENT_UP:
                                if (linkset->state != LINKSET_STATE_UP) {
@@ -710,9 +774,8 @@ void *ss7_linkset(void *data)
                                ast_log(LOG_WARNING, "MTP2 link down (SLC %d)\n", e->gen.data);
                                break;
                        case ISUP_EVENT_CPG:
-                               chanpos = ss7_find_cic(linkset, e->cpg.cic, e->cpg.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->cpg.cic, e->cpg.opc, "CPG");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "CPG on unconfigured CIC %d\n", e->cpg.cic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -753,17 +816,15 @@ void *ss7_linkset(void *data)
                                break;
                        case ISUP_EVENT_RSC:
                                ast_verbose("Resetting CIC %d\n", e->rsc.cic);
-                               chanpos = ss7_find_cic(linkset, e->rsc.cic, e->rsc.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->rsc.cic, e->rsc.opc, "RSC");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "RSC on unconfigured CIC %d\n", e->rsc.cic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
                                sig_ss7_lock_private(p);
                                sig_ss7_set_inservice(p, 1);
                                sig_ss7_set_remotelyblocked(p, 0);
-                               dpc = p->dpc;
-                               isup_set_call_dpc(e->rsc.call, dpc);
+                               isup_set_call_dpc(e->rsc.call, p->dpc);
                                sig_ss7_lock_owner(linkset, chanpos);
                                p->ss7call = NULL;
                                if (p->owner) {
@@ -775,9 +836,8 @@ void *ss7_linkset(void *data)
                                break;
                        case ISUP_EVENT_GRS:
                                ast_debug(1, "Got Reset for CICs %d to %d: Acknowledging\n", e->grs.startcic, e->grs.endcic);
-                               chanpos = ss7_find_cic(linkset, e->grs.startcic, e->grs.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->grs.startcic, e->grs.opc, "GRS");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "GRS on unconfigured CIC %d\n", e->grs.startcic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -796,29 +856,53 @@ void *ss7_linkset(void *data)
                                break;
                        case ISUP_EVENT_IAM:
                                ast_debug(1, "Got IAM for CIC %d and called number %s, calling number %s\n", e->iam.cic, e->iam.called_party_num, e->iam.calling_party_num);
-                               chanpos = ss7_find_cic(linkset, e->iam.cic, e->iam.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->iam.cic, e->iam.opc, "IAM");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "IAM on unconfigured CIC %d\n", e->iam.cic);
                                        isup_rel(ss7, e->iam.call, -1);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
                                sig_ss7_lock_private(p);
-                               if (p->owner) {
-                                       if (p->ss7call == e->iam.call) {
-                                               sig_ss7_unlock_private(p);
-                                               ast_log(LOG_WARNING, "Duplicate IAM requested on CIC %d\n", e->iam.cic);
-                                               break;
-                                       } else {
-                                               sig_ss7_unlock_private(p);
-                                               ast_log(LOG_WARNING, "Ring requested on CIC %d already in use!\n", e->iam.cic);
-                                               break;
+                               sig_ss7_lock_owner(linkset, chanpos);
+                               if (p->call_level != SIG_SS7_CALL_LEVEL_IDLE) {
+                                       /*
+                                        * Detected glare/dual-seizure
+                                        *
+                                        * Always abort both calls since we can't implement the dual
+                                        * seizure procedures due to our channel assignment architecture
+                                        * and the fact that we cannot tell libss7 to discard its call
+                                        * structure to ignore the incoming IAM.
+                                        */
+                                       ast_debug(1,
+                                               "Linkset %d: SS7 IAM glare on CIC/DPC %d/%d.  Dropping both calls.\n",
+                                               linkset->span, e->iam.cic, e->iam.opc);
+                                       if (p->call_level == SIG_SS7_CALL_LEVEL_ALLOCATED) {
+                                               /*
+                                                * We have not sent our IAM yet and we never will at this point.
+                                                */
+                                               p->alreadyhungup = 1;
+                                               isup_rel(ss7, e->iam.call, -1);
                                        }
+                                       p->call_level = SIG_SS7_CALL_LEVEL_GLARE;
+                                       if (p->owner) {
+                                               p->owner->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+                                               ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV);
+                                               ast_channel_unlock(p->owner);
+                                       }
+                                       sig_ss7_unlock_private(p);
+                                       break;
                                }
-
-                               dpc = p->dpc;
+                               /*
+                                * The channel should not have an owner at this point since we
+                                * are in the process of creating an owner for it.
+                                */
+                               ast_assert(!p->owner);
+
+                               /* Mark channel as in use so no outgoing call will steal it. */
+                               p->call_level = SIG_SS7_CALL_LEVEL_ALLOCATED;
                                p->ss7call = e->iam.call;
-                               isup_set_call_dpc(p->ss7call, dpc);
+
+                               isup_set_call_dpc(p->ss7call, p->dpc);
 
                                if ((p->use_callerid) && (!ast_strlen_zero(e->iam.calling_party_num))) {
                                        ss7_apply_plan_to_number(p->cid_num, sizeof(p->cid_num), linkset, e->iam.calling_party_num, e->iam.calling_nai);
@@ -830,8 +914,10 @@ void *ss7_linkset(void *data)
                                if (!ast_strlen_zero(e->iam.called_party_num)) {
                                        ss7_apply_plan_to_number(p->exten, sizeof(p->exten), linkset,
                                                e->iam.called_party_num, e->iam.called_nai);
-                                       sig_ss7_set_dnid(p, p->exten);
+                               } else {
+                                       p->exten[0] = '\0';
                                }
+                               sig_ss7_set_dnid(p, p->exten);
 
                                if (p->immediate) {
                                        p->exten[0] = 's';
@@ -874,9 +960,11 @@ void *ss7_linkset(void *data)
 
                                if (ast_exists_extension(NULL, p->context, p->exten, 1, p->cid_num)) {
                                        if (e->iam.cot_check_required) {
+                                               p->call_level = SIG_SS7_CALL_LEVEL_CONTINUITY;
                                                sig_ss7_loopback(p, 1);
-                                       } else
+                                       } else {
                                                ss7_start_call(p, linkset);
+                                       }
                                } else {
                                        ast_debug(1, "Call on CIC for unconfigured extension %s\n", p->exten);
                                        p->alreadyhungup = 1;
@@ -885,9 +973,8 @@ void *ss7_linkset(void *data)
                                sig_ss7_unlock_private(p);
                                break;
                        case ISUP_EVENT_COT:
-                               chanpos = ss7_find_cic(linkset, e->cot.cic, e->cot.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->cot.cic, e->cot.opc, "COT");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "COT on unconfigured CIC %d\n", e->cot.cic);
                                        isup_rel(ss7, e->cot.call, -1);
                                        break;
                                }
@@ -902,9 +989,8 @@ void *ss7_linkset(void *data)
                                break;
                        case ISUP_EVENT_CCR:
                                ast_debug(1, "Got CCR request on CIC %d\n", e->ccr.cic);
-                               chanpos = ss7_find_cic(linkset, e->ccr.cic, e->ccr.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->ccr.cic, e->ccr.opc, "CCR");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "CCR on unconfigured CIC %d\n", e->ccr.cic);
                                        break;
                                }
 
@@ -918,9 +1004,8 @@ void *ss7_linkset(void *data)
                                break;
                        case ISUP_EVENT_CVT:
                                ast_debug(1, "Got CVT request on CIC %d\n", e->cvt.cic);
-                               chanpos = ss7_find_cic(linkset, e->cvt.cic, e->cvt.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->cvt.cic, e->cvt.opc, "CVT");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "CVT on unconfigured CIC %d\n", e->cvt.cic);
                                        break;
                                }
 
@@ -933,9 +1018,10 @@ void *ss7_linkset(void *data)
                                isup_cvr(linkset->ss7, e->cvt.cic, p->dpc);
                                break;
                        case ISUP_EVENT_REL:
-                               chanpos = ss7_find_cic(linkset, e->rel.cic, e->rel.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->rel.cic, e->rel.opc, "REL");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "REL on unconfigured CIC %d\n", e->rel.cic);
+                                       /* Continue hanging up the call anyway. */
+                                       isup_rlc(ss7, e->rel.call);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -945,8 +1031,6 @@ void *ss7_linkset(void *data)
                                        p->owner->hangupcause = e->rel.cause;
                                        ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV);
                                        ast_channel_unlock(p->owner);
-                               } else {
-                                       ast_log(LOG_WARNING, "REL on channel (CIC %d) without owner!\n", p->cic);
                                }
 
                                /* End the loopback if we have one */
@@ -958,12 +1042,12 @@ void *ss7_linkset(void *data)
                                sig_ss7_unlock_private(p);
                                break;
                        case ISUP_EVENT_ACM:
-                               chanpos = ss7_find_cic(linkset, e->acm.cic, e->acm.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->acm.cic, e->acm.opc, "ACM");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "ACM on unconfigured CIC %d\n", e->acm.cic);
                                        isup_rel(ss7, e->acm.call, -1);
                                        break;
-                               } else {
+                               }
+                               {
                                        p = linkset->pvts[chanpos];
 
                                        ast_debug(1, "Queueing frame from SS7_EVENT_ACM on CIC %d\n", p->cic);
@@ -994,9 +1078,8 @@ void *ss7_linkset(void *data)
                                }
                                break;
                        case ISUP_EVENT_CGB:
-                               chanpos = ss7_find_cic(linkset, e->cgb.startcic, e->cgb.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->cgb.startcic, e->cgb.opc, "CGB");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "CGB on unconfigured CIC %d\n", e->cgb.startcic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -1004,9 +1087,8 @@ void *ss7_linkset(void *data)
                                isup_cgba(linkset->ss7, e->cgb.startcic, e->cgb.endcic, e->cgb.opc, e->cgb.status, e->cgb.type);
                                break;
                        case ISUP_EVENT_CGU:
-                               chanpos = ss7_find_cic(linkset, e->cgu.startcic, e->cgu.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->cgu.startcic, e->cgu.opc, "CGU");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "CGU on unconfigured CIC %d\n", e->cgu.startcic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -1014,9 +1096,8 @@ void *ss7_linkset(void *data)
                                isup_cgua(linkset->ss7, e->cgu.startcic, e->cgu.endcic, e->cgu.opc, e->cgu.status, e->cgu.type);
                                break;
                        case ISUP_EVENT_UCIC:
-                               chanpos = ss7_find_cic(linkset, e->ucic.cic, e->ucic.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->ucic.cic, e->ucic.opc, "UCIC");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "UCIC on unconfigured CIC %d\n", e->ucic.cic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -1027,9 +1108,8 @@ void *ss7_linkset(void *data)
                                sig_ss7_unlock_private(p);/* doesn't require a SS7 acknowledgement */
                                break;
                        case ISUP_EVENT_BLO:
-                               chanpos = ss7_find_cic(linkset, e->blo.cic, e->blo.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->blo.cic, e->blo.opc, "BLO");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "BLO on unconfigured CIC %d\n", e->blo.cic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -1040,9 +1120,8 @@ void *ss7_linkset(void *data)
                                isup_bla(linkset->ss7, e->blo.cic, p->dpc);
                                break;
                        case ISUP_EVENT_BLA:
-                               chanpos = ss7_find_cic(linkset, e->bla.cic, e->bla.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->bla.cic, e->bla.opc, "BLA");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "BLA on unconfigured CIC %d\n", e->bla.cic);
                                        break;
                                }
                                ast_debug(1, "Blocking CIC %d\n", e->bla.cic);
@@ -1052,9 +1131,8 @@ void *ss7_linkset(void *data)
                                sig_ss7_unlock_private(p);
                                break;
                        case ISUP_EVENT_UBL:
-                               chanpos = ss7_find_cic(linkset, e->ubl.cic, e->ubl.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->ubl.cic, e->ubl.opc, "UBL");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "UBL on unconfigured CIC %d\n", e->ubl.cic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -1065,9 +1143,8 @@ void *ss7_linkset(void *data)
                                isup_uba(linkset->ss7, e->ubl.cic, p->dpc);
                                break;
                        case ISUP_EVENT_UBA:
-                               chanpos = ss7_find_cic(linkset, e->uba.cic, e->uba.opc);
+                               chanpos = ss7_find_cic_gripe(linkset, e->uba.cic, e->uba.opc, "UBA");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "UBA on unconfigured CIC %d\n", e->uba.cic);
                                        break;
                                }
                                p = linkset->pvts[chanpos];
@@ -1078,17 +1155,21 @@ void *ss7_linkset(void *data)
                                break;
                        case ISUP_EVENT_CON:
                        case ISUP_EVENT_ANM:
-                               if (e->e == ISUP_EVENT_CON)
-                                       cic = e->con.cic;
-                               else
-                                       cic = e->anm.cic;
-
-                               chanpos = ss7_find_cic(linkset, cic, (e->e == ISUP_EVENT_ANM) ? e->anm.opc : e->con.opc);
-                               if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "ANM/CON on unconfigured CIC %d\n", cic);
-                                       isup_rel(ss7, (e->e == ISUP_EVENT_ANM) ? e->anm.call : e->con.call, -1);
-                                       break;
+                               if (e->e == ISUP_EVENT_CON) {
+                                       chanpos = ss7_find_cic_gripe(linkset, e->con.cic, e->con.opc, "CON");
+                                       if (chanpos < 0) {
+                                               isup_rel(ss7, e->con.call, -1);
+                                               break;
+                                       }
                                } else {
+                                       chanpos = ss7_find_cic_gripe(linkset, e->anm.cic, e->anm.opc, "ANM");
+                                       if (chanpos < 0) {
+                                               isup_rel(ss7, e->anm.call, -1);
+                                               break;
+                                       }
+                               }
+
+                               {
                                        p = linkset->pvts[chanpos];
                                        sig_ss7_lock_private(p);
                                        if (p->call_level < SIG_SS7_CALL_LEVEL_CONNECT) {
@@ -1106,30 +1187,43 @@ void *ss7_linkset(void *data)
                                }
                                break;
                        case ISUP_EVENT_RLC:
-                               chanpos = ss7_find_cic(linkset, e->rlc.cic, e->rlc.opc);
+                               /* XXX Call ptr should be passed up from libss7! */
+                               chanpos = ss7_find_cic_gripe(linkset, e->rlc.cic, e->rlc.opc, "RLC");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "RLC on unconfigured CIC %d\n", e->rlc.cic);
                                        break;
-                               } else {
+                               }
+                               {
                                        p = linkset->pvts[chanpos];
                                        sig_ss7_lock_private(p);
-                                       if (p->alreadyhungup)
+                                       if (p->alreadyhungup) {
+                                               if (!p->owner) {
+                                                       p->call_level = SIG_SS7_CALL_LEVEL_IDLE;
+                                               }
                                                p->ss7call = NULL;
-                                       else
-                                               ast_log(LOG_NOTICE, "Received RLC out and we haven't sent REL.  Ignoring.\n");
+                                       }
                                        sig_ss7_unlock_private(p);
                                }
                                break;
                        case ISUP_EVENT_FAA:
-                               chanpos = ss7_find_cic(linkset, e->faa.cic, e->faa.opc);
+                               /*!
+                                * \todo The handling of the SS7 FAA message is not good and I
+                                * don't know enough to handle it correctly.
+                                */
+                               chanpos = ss7_find_cic_gripe(linkset, e->faa.cic, e->faa.opc, "FAA");
                                if (chanpos < 0) {
-                                       ast_log(LOG_WARNING, "FAA on unconfigured CIC %d\n", e->faa.cic);
+                                       isup_rel(linkset->ss7, e->faa.call, -1);
                                        break;
-                               } else {
+                               }
+                               {
+                                       /* XXX FAR and FAA used for something dealing with transfers? */
                                        p = linkset->pvts[chanpos];
                                        ast_debug(1, "FAA received on CIC %d\n", e->faa.cic);
                                        sig_ss7_lock_private(p);
                                        if (p->alreadyhungup){
+                                               if (!p->owner) {
+                                                       p->call_level = SIG_SS7_CALL_LEVEL_IDLE;
+                                               }
+                                               /* XXX We seem to be leaking the isup call structure here. */
                                                p->ss7call = NULL;
                                                ast_log(LOG_NOTICE, "Received FAA and we haven't sent FAR.  Ignoring.\n");
                                        }
@@ -1247,6 +1341,24 @@ int sig_ss7_add_sigchan(struct sig_ss7_linkset *linkset, int which, int ss7type,
 }
 
 /*!
+ * \internal
+ * \brief Determine if a private channel structure is available.
+ *
+ * \param pvt Channel to determine if available.
+ *
+ * \return TRUE if the channel is available.
+ */
+static int sig_ss7_is_chan_available(struct sig_ss7_chan *pvt)
+{
+       if (!pvt->inalarm && !pvt->owner && !pvt->ss7call
+               && pvt->call_level == SIG_SS7_CALL_LEVEL_IDLE
+               && !pvt->locallyblocked && !pvt->remotelyblocked) {
+               return 1;
+       }
+       return 0;
+}
+
+/*!
  * \brief Determine if the specified channel is available for an outgoing call.
  * \since 1.8
  *
@@ -1256,17 +1368,22 @@ int sig_ss7_add_sigchan(struct sig_ss7_linkset *linkset, int which, int ss7type,
  */
 int sig_ss7_available(struct sig_ss7_chan *p)
 {
+       int available;
+
        if (!p->ss7) {
                /* Something is wrong here.  A SS7 channel without the ss7 pointer? */
                return 0;
        }
 
-       if (!p->inalarm && !p->owner && !p->ss7call
-               && !p->locallyblocked && !p->remotelyblocked) {
-               return 1;
+       /* Only have to deal with the linkset lock. */
+       ast_mutex_lock(&p->ss7->lock);
+       available = sig_ss7_is_chan_available(p);
+       if (available) {
+               p->call_level = SIG_SS7_CALL_LEVEL_ALLOCATED;
        }
+       ast_mutex_unlock(&p->ss7->lock);
 
-       return 0;
+       return available;
 }
 
 static unsigned char cid_pres2ss7pres(int cid_pres)
@@ -1333,6 +1450,12 @@ int sig_ss7_call(struct sig_ss7_chan *p, struct ast_channel *ast, char *rdest)
 
        ss7_grab(p, p->ss7);
 
+       if (p->call_level != SIG_SS7_CALL_LEVEL_ALLOCATED) {
+               /* Call collision before sending IAM.  Abort call. */
+               ss7_rel(p->ss7);
+               return -1;
+       }
+
        p->ss7call = isup_new_call(p->ss7->ss7);
        if (!p->ss7call) {
                ss7_rel(p->ss7);
@@ -1447,14 +1570,14 @@ int sig_ss7_hangup(struct sig_ss7_chan *p, struct ast_channel *ast)
 
        p->owner = NULL;
        sig_ss7_set_dialing(p, 0);
-       p->call_level = SIG_SS7_CALL_LEVEL_IDLE;
        p->outgoing = 0;
        p->progress = 0;
        p->rlt = 0;
        p->exten[0] = '\0';
        /* Perform low level hangup if no owner left */
+       ss7_grab(p, p->ss7);
+       p->call_level = SIG_SS7_CALL_LEVEL_IDLE;
        if (p->ss7call) {
-               ss7_grab(p, p->ss7);
                if (!p->alreadyhungup) {
                        const char *cause = pbx_builtin_getvar_helper(ast,"SS7_CAUSE");
                        int icause = ast->hangupcause ? ast->hangupcause : -1;
@@ -1466,11 +1589,9 @@ int sig_ss7_hangup(struct sig_ss7_chan *p, struct ast_channel *ast)
                        }
                        isup_rel(p->ss7->ss7, p->ss7call, icause);
                        p->alreadyhungup = 1;
-               } else {
-                       ast_log(LOG_WARNING, "Trying to hangup twice!\n");
                }
-               ss7_rel(p->ss7);
        }
+       ss7_rel(p->ss7);
 
        return res;
 }
@@ -1537,17 +1658,19 @@ int sig_ss7_indicate(struct sig_ss7_chan *p, struct ast_channel *chan, int condi
                res = sig_ss7_play_tone(p, SIG_SS7_TONE_BUSY);
                break;
        case AST_CONTROL_RINGING:
+               ss7_grab(p, p->ss7);
                if (p->call_level < SIG_SS7_CALL_LEVEL_ALERTING && !p->outgoing) {
                        p->call_level = SIG_SS7_CALL_LEVEL_ALERTING;
-                       if (p->ss7 && p->ss7->ss7) {
-                               ss7_grab(p, p->ss7);
-                               if ((isup_far(p->ss7->ss7, p->ss7call)) != -1)
-                                       p->rlt = 1;
-                               if (p->rlt != 1) /* No need to send CPG if call will be RELEASE */
-                                       isup_cpg(p->ss7->ss7, p->ss7call, CPG_EVENT_ALERTING);
-                               ss7_rel(p->ss7);
+                       if ((isup_far(p->ss7->ss7, p->ss7call)) != -1) {
+                               p->rlt = 1;
+                       }
+
+                       /* No need to send CPG if call will be RELEASE */
+                       if (p->rlt != 1) {
+                               isup_cpg(p->ss7->ss7, p->ss7call, CPG_EVENT_ALERTING);
                        }
                }
+               ss7_rel(p->ss7);
 
                res = sig_ss7_play_tone(p, SIG_SS7_TONE_RINGTONE);
 
@@ -1557,37 +1680,34 @@ int sig_ss7_indicate(struct sig_ss7_chan *p, struct ast_channel *chan, int condi
                break;
        case AST_CONTROL_PROCEEDING:
                ast_debug(1,"Received AST_CONTROL_PROCEEDING on %s\n",chan->name);
+               ss7_grab(p, p->ss7);
                /* This IF sends the FAR for an answered ALEG call */
                if (chan->_state == AST_STATE_UP && (p->rlt != 1)){
-                       ss7_grab(p, p->ss7);
                        if ((isup_far(p->ss7->ss7, p->ss7call)) != -1) {
                                p->rlt = 1;
                        }
-                       ss7_rel(p->ss7);
                }
 
                if (p->call_level < SIG_SS7_CALL_LEVEL_PROCEEDING && !p->outgoing) {
                        p->call_level = SIG_SS7_CALL_LEVEL_PROCEEDING;
-                       if (p->ss7 && p->ss7->ss7) {
-                               ss7_grab(p, p->ss7);
-                               isup_acm(p->ss7->ss7, p->ss7call);
-                               ss7_rel(p->ss7);
-                       }
+                       isup_acm(p->ss7->ss7, p->ss7call);
                }
+               ss7_rel(p->ss7);
                /* don't continue in ast_indicate */
                res = 0;
                break;
        case AST_CONTROL_PROGRESS:
                ast_debug(1,"Received AST_CONTROL_PROGRESS on %s\n",chan->name);
+               ss7_grab(p, p->ss7);
                if (!p->progress && p->call_level < SIG_SS7_CALL_LEVEL_ALERTING && !p->outgoing) {
                        p->progress = 1;/* No need to send inband-information progress again. */
-                       if (p->ss7 && p->ss7->ss7) {
-                               ss7_grab(p, p->ss7);
-                               isup_cpg(p->ss7->ss7, p->ss7call, CPG_EVENT_INBANDINFO);
-                               ss7_rel(p->ss7);
-                               /* enable echo canceler here on SS7 calls */
-                               sig_ss7_set_echocanceller(p, 1);
-                       }
+                       isup_cpg(p->ss7->ss7, p->ss7call, CPG_EVENT_INBANDINFO);
+                       ss7_rel(p->ss7);
+
+                       /* enable echo canceler here on SS7 calls */
+                       sig_ss7_set_echocanceller(p, 1);
+               } else {
+                       ss7_rel(p->ss7);
                }
                /* don't continue in ast_indicate */
                res = 0;
@@ -1639,6 +1759,11 @@ struct ast_channel *sig_ss7_request(struct sig_ss7_chan *p, enum sig_ss7_law law
        ast = sig_ss7_new_ast_channel(p, AST_STATE_RESERVED, law, transfercapability, p->exten, requestor);
        if (!ast) {
                p->outgoing = 0;
+
+               /* Release the allocated channel.  Only have to deal with the linkset lock. */
+               ast_mutex_lock(&p->ss7->lock);
+               p->call_level = SIG_SS7_CALL_LEVEL_IDLE;
+               ast_mutex_unlock(&p->ss7->lock);
        }
        return ast;
 }
@@ -1656,6 +1781,51 @@ void sig_ss7_chan_delete(struct sig_ss7_chan *doomed)
        ast_free(doomed);
 }
 
+#define SIG_SS7_SC_HEADER      "%-4s %4s %-4s %-3s %-3s %-10s %-4s %s\n"
+#define SIG_SS7_SC_LINE                 "%4d %4d %-4s %-3s %-3s %-10s %-4s %s"
+void sig_ss7_cli_show_channels_header(int fd)
+{
+       ast_cli(fd, SIG_SS7_SC_HEADER, "link", "",     "Chan", "Lcl", "Rem", "Call",  "SS7",  "Channel");
+       ast_cli(fd, SIG_SS7_SC_HEADER, "set",  "Chan", "Idle", "Blk", "Blk", "Level", "Call", "Name");
+}
+
+void sig_ss7_cli_show_channels(int fd, struct sig_ss7_linkset *linkset)
+{
+       char line[256];
+       int idx;
+       struct sig_ss7_chan *pvt;
+
+       ast_mutex_lock(&linkset->lock);
+       for (idx = 0; idx < linkset->numchans; ++idx) {
+               if (!linkset->pvts[idx]) {
+                       continue;
+               }
+               pvt = linkset->pvts[idx];
+               sig_ss7_lock_private(pvt);
+               sig_ss7_lock_owner(linkset, idx);
+
+               snprintf(line, sizeof(line), SIG_SS7_SC_LINE,
+                       linkset->span,
+                       pvt->channel,
+                       sig_ss7_is_chan_available(pvt) ? "Yes" : "No",
+                       pvt->locallyblocked ? "Yes" : "No",
+                       pvt->remotelyblocked ? "Yes" : "No",
+                       sig_ss7_call_level2str(pvt->call_level),
+                       pvt->ss7call ? "Yes" : "No",
+                       pvt->owner ? pvt->owner->name : "");
+
+               if (pvt->owner) {
+                       ast_channel_unlock(pvt->owner);
+               }
+               sig_ss7_unlock_private(pvt);
+
+               ast_mutex_unlock(&linkset->lock);
+               ast_cli(fd, "%s\n", line);
+               ast_mutex_lock(&linkset->lock);
+       }
+       ast_mutex_unlock(&linkset->lock);
+}
+
 /*!
  * \brief Create a new sig_ss7 private channel structure.
  * \since 1.8
@@ -1687,7 +1857,7 @@ struct sig_ss7_chan *sig_ss7_chan_new(void *pvt_data, struct sig_ss7_callback *c
  * \brief Initialize the SS7 linkset control.
  * \since 1.8
  *
- * \param ss7 sig_ss7 SS7 control structure.
+ * \param ss7 SS7 linkset control structure.
  *
  * \return Nothing
  */