freetdm: Clean up SS7 native bridge code to separate the call control, queuing and

resource-cleanup responsibilities clearly between the 2 channels involved in the bridge

   - Each channel is responsible for clearning its own peer_data and event queue
     at the end of the call (when moving to DOWN state)

   - Each channel dequeues messages only from its own queue and enqueues messages
     in the peer's queue, with the only exception being messages received before
     the bridge is stablished (IAM for sure and possible SAM messages) because
     if the bridge is not yet stablished the messages must be queued by the channel
     in its own queue temporarily until the bridge is ready

   - When the bridge is ready it is the responsibility of the incoming channel to
     move the messages that stored temporarily in its own queue to the bridged peer queue

   - During hangup, each channel is responsible for moving itself to DOWN. The procedure
     however differs slightly depending on the hangup conditions

     If the user requests hangup (ie, FreeSWITCH) the request will be noted by setting the
     FTDM_CHANNEL_USER_HANGUP flag but will not be processed yet because call control is
     driven only by the link messages (so no hangup from ESL or command line allowed)

     When REL message comes, the channel receiving it must move to TERMINATING state and:

           - If the user has not hangup yet (FTDM_CHANNEL_USER_HANGUP flag not set) then
             notify the user via SIGEVENT_STOP and wait for the user to move to HANGUP
             state by calling ftdm_channel_call_hangup() before sending RLC

           - If the user did hangup already (FTDM_CHANNEL_USER_HANGUP flag is set) then
             skip user notification and move to HANGUP state directly where the RLC message
             will be sent

   - On HANGUP state the RLC is sent and the channel is moved to DOWN, final state
     The peer channel will forward the REL message and wait for RLC from the network, when
     RLC is received the channel can move straight to DOWN itself because the peer channel
     is completing its own shutdown procedure when it received the REL message
This commit is contained in:
James Zhang 2012-04-03 11:23:49 -04:00
parent 8a589435de
commit b324f86797
4 changed files with 88 additions and 123 deletions

View File

@ -81,6 +81,7 @@ void sngss7_con_ind(uint32_t suInstId, uint32_t spInstId, uint32_t circuit, SiCo
/* initalize the sngss7_event */
sngss7_event = ftdm_malloc(sizeof(*sngss7_event));
if (sngss7_event == NULL) {
SS7_ERROR("Failed to allocate memory for sngss7_event!\n");
SS7_FUNC_TRACE_EXIT(__FUNCTION__);

View File

@ -340,10 +340,9 @@ static void handle_hw_alarm(ftdm_event_t *e)
/* MONITIOR THREADS ***********************************************************/
static void *ftdm_sangoma_ss7_run(ftdm_thread_t * me, void *obj)
{
ftdm_interrupt_t *ftdm_sangoma_ss7_int[3];
ftdm_interrupt_t *ftdm_sangoma_ss7_int[2];
ftdm_span_t *ftdmspan = (ftdm_span_t *) obj;
ftdm_channel_t *ftdmchan = NULL;
ftdm_channel_t *peerchan = NULL;
ftdm_event_t *event = NULL;
sngss7_event_data_t *sngss7_event = NULL;
sngss7_span_data_t *sngss7_span = (sngss7_span_data_t *)ftdmspan->signal_data;
@ -368,12 +367,6 @@ static void *ftdm_sangoma_ss7_run(ftdm_thread_t * me, void *obj)
goto ftdm_sangoma_ss7_run_exit;
}
/* get an interrupt queue for this span for peer channel events */
if (ftdm_queue_get_interrupt (sngss7_span->peer_chans, &ftdm_sangoma_ss7_int[2]) != FTDM_SUCCESS) {
SS7_CRITICAL ("Failed to get a ftdm_interrupt for span = %d for peer channel events queue!\n", ftdmspan->span_id);
goto ftdm_sangoma_ss7_run_exit;
}
while (ftdm_running () && !(ftdm_test_flag (ftdmspan, FTDM_SPAN_STOP_THREAD))) {
int x = 0;
if (b_alarm_test) {
@ -410,51 +403,26 @@ static void *ftdm_sangoma_ss7_run(ftdm_thread_t * me, void *obj)
/* clean out all pending channel state changes */
while ((ftdmchan = ftdm_queue_dequeue (ftdmspan->pendingchans))) {
sngss7_chan_data_t *chan_info = ftdmchan->call_data;
/*first lock the channel */
ftdm_mutex_lock(ftdmchan->mutex);
/* process state changes for this channel until they are all done */
ftdm_channel_advance_states(ftdmchan);
if (chan_info->peer_data) {
/* clean out all pending stack events in the peer channel */
while ((sngss7_event = ftdm_queue_dequeue(chan_info->event_queue))) {
ftdm_sangoma_ss7_process_peer_stack_event(ftdmchan, sngss7_event);
ftdm_safe_free(sngss7_event);
}
}
/* unlock the channel */
ftdm_mutex_unlock (ftdmchan->mutex);
}
/* clean out all peer pending channel events */
while ((peerchan = ftdm_queue_dequeue (sngss7_span->peer_chans))) {
/* note that the channels being dequeued here may not belong to this span
they may belong to just about any other span that one of our channels
happens to be bridged to */
sngss7_chan_data_t *peer_info;
sngss7_chan_data_t *chan_info;
peer_info = peerchan->call_data;
if (peer_info) {
chan_info = peer_info->peer_data;
if (chan_info) {
ftdmchan = chan_info->ftdmchan;
if (ftdmchan) {
/*
if there is any state changes at all, those will be done in the opposite channel
to peerchan (where the original event was received), therefore we must lock ftdmchan,
but do not need to lock peerchan as we only read its event queue, which is already
locked when dequeueing */
ftdm_channel_lock(ftdmchan);
/* clean out all pending stack events in the peer channel */
while ((sngss7_event = ftdm_queue_dequeue(peer_info->event_queue))) {
ftdm_sangoma_ss7_process_peer_stack_event(ftdmchan, sngss7_event);
ftdm_safe_free(sngss7_event);
}
ftdm_channel_unlock(ftdmchan);
}
}
}
}
/* clean out all pending stack events */
while ((sngss7_event = ftdm_queue_dequeue(sngss7_span->event_queue))) {
ftdm_sangoma_ss7_process_stack_event(sngss7_event);
@ -570,16 +538,16 @@ static void ftdm_sangoma_ss7_process_stack_event (sngss7_event_data_t *sngss7_ev
ftdm_channel_advance_states(ftdmchan);
if (sngss7_event->event_id == SNGSS7_CON_IND_EVENT) {
/* this is the first event in a call, flush the event queue */
sngss7_flush_queue(sngss7_info->event_queue);
/* clear the peer if any */
sngss7_info->peer_data = NULL;
clone_event++;
}
/* if the call has already started and the event is not a release confirmation, clone the event */
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_CALL_STARTED) &&
sngss7_event->event_id != SNGSS7_REL_CFM_EVENT) {
/* If the call has already started (we only bridge events related to calls)
* and the event is not a release confirmation, then clone the event.
* We do not clone release cfm events because that is the only event (final event) that is not
* bridged to the other leg, the first Spirou customer we had explicitly requested to send
* release confirm as soon as the release is received and therefore not wait for the other leg
* to send release confirm (hence, not need to clone and enqueue in the other leg) */
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_CALL_STARTED) && sngss7_event->event_id != SNGSS7_REL_CFM_EVENT) {
clone_event++;
}
@ -606,11 +574,38 @@ static void ftdm_sangoma_ss7_process_stack_event (sngss7_event_data_t *sngss7_ev
event_clone = ftdm_calloc(1, sizeof(*sngss7_event));
if (event_clone) {
memcpy(event_clone, sngss7_event, sizeof(*sngss7_event));
ftdm_queue_enqueue(sngss7_info->event_queue, event_clone);
/* if we have already a peer channel then enqueue the event in their queue */
if (sngss7_info->peer_data) {
sngss7_span_data_t *sngss7_peer_span = (sngss7_span_data_t *)sngss7_info->peer_data->ftdmchan->span->signal_data;
ftdm_span_t *peer_span = sngss7_info->peer_data->ftdmchan->span;
if (sngss7_info->peer_event_transfer_cnt) {
sngss7_event_data_t *peer_event = NULL;
int qi = 0;
/* looks like for the first time we found our peer, transfer any messages we enqueued */
for (qi = 0; qi < sngss7_info->peer_event_transfer_cnt; qi++) {
peer_event = ftdm_queue_dequeue(sngss7_info->event_queue);
if (peer_event) {
ftdm_queue_enqueue(sngss7_info->peer_data->event_queue, peer_event);
} else {
/* This should never happen! */
SS7_CRIT_CHAN(ftdmchan,"[CIC:%d]What!? someone stole my messages!\n", sngss7_info->circuit->cic);
}
}
SS7_DEBUG_CHAN(ftdmchan,"[CIC:%d]Transferred %d messages into my peer's queue\n",
sngss7_info->circuit->cic, sngss7_info->peer_event_transfer_cnt);
sngss7_info->peer_event_transfer_cnt = 0;
}
/* we already have a peer attached, wake him up */
ftdm_queue_enqueue(sngss7_peer_span->peer_chans, sngss7_info->ftdmchan);
ftdm_queue_enqueue(sngss7_info->peer_data->event_queue, event_clone);
ftdm_queue_enqueue(peer_span->pendingchans, sngss7_info->peer_data->ftdmchan);
} else {
/* we don't have a peer yet, save the event on our own queue for later
* only the first event in this queue is directly consumed by our peer (IAM), subsequent events
* must be transferred by us to their queue as soon as we find our peer */
ftdm_queue_enqueue(sngss7_info->event_queue, event_clone);
if (sngss7_event->event_id != SNGSS7_CON_IND_EVENT) {
/* This could be an SAM, save it for transfer once we know who our peer is (if we ever find that) */
sngss7_info->peer_event_transfer_cnt++;
}
}
}
}
@ -623,25 +618,7 @@ static void ftdm_sangoma_ss7_process_stack_event (sngss7_event_data_t *sngss7_ev
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_TERMINATING);
break;
case SNGSS7_REL_CFM_EVENT:
{
if (sngss7_info->peer_data) {
ftdm_channel_t *peer_chan = sngss7_info->peer_data->ftdmchan;
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
if (peer_chan) {
/* we need to unlock our chan or we risk deadlock */
ftdm_channel_advance_states(ftdmchan);
ftdm_channel_unlock(ftdmchan);
ftdm_channel_lock(peer_chan);
if (peer_chan->state != FTDM_CHANNEL_STATE_DOWN) {
ftdm_set_state(peer_chan, FTDM_CHANNEL_STATE_DOWN);
}
ftdm_channel_unlock(peer_chan);
ftdm_channel_lock(ftdmchan);
}
}
}
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
break;
default:
break;
@ -1021,6 +998,7 @@ static ftdm_status_t ftdm_sangoma_ss7_native_bridge_state_change(ftdm_channel_t
sngss7_clear_ckt_flag(sngss7_info, FLAG_SUS_RECVD);
sngss7_clear_ckt_flag(sngss7_info, FLAG_T6_CANCELED);
sngss7_flush_queue(sngss7_info->event_queue);
sngss7_info->peer_data = NULL;
ftdm_channel_close (&close_chan);
}
break;
@ -1035,9 +1013,22 @@ static ftdm_status_t ftdm_sangoma_ss7_native_bridge_state_change(ftdm_channel_t
case FTDM_CHANNEL_STATE_TERMINATING:
{
ft_to_sngss7_rlc(ftdmchan);
/* Release confirm is sent immediately, since Spirou customer asked us not to wait for the second call leg
* to come back with a release confirm ... */
/* when receiving REL we move to TERMINATING and notify the user that the bridge is ending */
sngss7_send_signal(sngss7_info, FTDM_SIGEVENT_STOP);
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_USER_HANGUP)) {
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_HANGUP);
} else {
/* Notify the user and wait for their ack before sending RLC */
sngss7_send_signal(sngss7_info, FTDM_SIGEVENT_STOP);
}
}
break;
case FTDM_CHANNEL_STATE_HANGUP:
{
ft_to_sngss7_rlc(ftdmchan);
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
}
break;
@ -1062,11 +1053,11 @@ ftdm_status_t ftdm_sangoma_ss7_process_state_change (ftdm_channel_t *ftdmchan)
sngss7_info->blk_flags);
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_NATIVE_SIGBRIDGE)) {
/* DIALING is the only state we process normally when doing an outgoing call that is natively bridged */
/* DIALING is the only state we process normally when doing an outgoing call that is natively bridged,
* all other states are run by a different state machine (and the freetdm core does not do any checking) */
if (ftdmchan->state != FTDM_CHANNEL_STATE_DIALING) {
return ftdm_sangoma_ss7_native_bridge_state_change(ftdmchan);
}
sngss7_info->peer_data = NULL;
}
/*check what state we are supposed to be in */
@ -1521,24 +1512,8 @@ ftdm_status_t ftdm_sangoma_ss7_process_state_change (ftdm_channel_t *ftdmchan)
if (ftdm_test_flag (ftdmchan, FTDM_CHANNEL_OPEN)) {
ftdm_channel_t *close_chan = ftdmchan;
/* detach native bridging if needed (only the outbound leg is responsible for that)
Inbound leg was responsible of flushing its queue of events, but peer attach/detach
is left as an outbound leg responsibility
*/
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OUTBOUND)) {
sngss7_chan_data_t *peer_info = sngss7_info->peer_data;
if (peer_info) {
sngss7_info->peer_data = NULL;
if (peer_info) {
peer_info->peer_data = NULL;
}
}
}
/* close the channel */
SS7_DEBUG_CHAN(ftdmchan,"FTDM Channel Close %s\n", "");
sngss7_flush_queue(sngss7_info->event_queue);
ftdm_channel_close (&close_chan);
}
@ -2415,12 +2390,6 @@ static FIO_CONFIGURE_SPAN_SIGNALING_FUNCTION(ftdm_sangoma_ss7_span_config)
return FTDM_FAIL;
}
/* create an peer channel queue for this span */
if ((ftdm_queue_create(&(ss7_span_info)->peer_chans, SNGSS7_PEER_CHANS_QUEUE_SIZE)) != FTDM_SUCCESS) {
SS7_CRITICAL("Unable to create peer chans queue!\n");
return FTDM_FAIL;
}
/*setup the span structure with the info so far */
g_ftdm_sngss7_data.sig_cb = sig_cb;
span->start = ftdm_sangoma_ss7_start;

View File

@ -522,7 +522,8 @@ typedef struct sngss7_chan_data {
sngss7_group_data_t tx_grs;
sngss7_group_data_t ucic;
ftdm_queue_t *event_queue;
struct sngss7_chan_data *peer_data;
struct sngss7_chan_data *peer_data;
uint8_t peer_event_transfer_cnt;
} sngss7_chan_data_t;
#define SNGSS7_RX_GRS_PENDING (1 << 0)
@ -536,7 +537,6 @@ typedef struct sngss7_span_data {
sngss7_group_data_t rx_cgu;
sngss7_group_data_t tx_cgu;
ftdm_queue_t *event_queue;
ftdm_queue_t *peer_chans;
} sngss7_span_data_t;
typedef struct sngss7_event_data
@ -970,7 +970,7 @@ if (ftdmchan->state == new_state) { \
#define SS7_INFO_CHAN(fchan, msg, args...) ftdm_log_chan(fchan, FTDM_LOG_INFO, msg , ##args)
#define SS7_WARN_CHAN(fchan, msg, args...) ftdm_log_chan(fchan, FTDM_LOG_WARNING, msg , ##args)
#define SS7_ERROR_CHAN(fchan, msg, args...) ftdm_log_chan(fchan, FTDM_LOG_ERROR, msg , ##args)
#define SS7_CTRIT_CHAN(fchan, msg, args...) ftdm_log_chan(fchan, FTDM_LOG_CRIT, msg , ##args)
#define SS7_CRIT_CHAN(fchan, msg, args...) ftdm_log_chan(fchan, FTDM_LOG_CRIT, msg , ##args)
#ifdef SS7_CODE_DEVEL
#define SS7_DEVEL_DEBUG(a,...) ftdm_log(FTDM_LOG_DEBUG,a,##__VA_ARGS__ );

View File

@ -48,6 +48,7 @@ void ft_to_sngss7_iam (ftdm_channel_t * ftdmchan)
SiConEvnt iam;
ftdm_bool_t native_going_up = FTDM_FALSE;
sngss7_chan_data_t *sngss7_info = ftdmchan->call_data;;
sngss7_event_data_t *event_clone = NULL;
SS7_FUNC_TRACE_ENTER (__FUNCTION__);
@ -72,33 +73,28 @@ void ft_to_sngss7_iam (ftdm_channel_t * ftdmchan)
var, peer_span->signal_type);
} else {
peer_info = peer_chan->call_data;
if (peer_info) {
SS7_INFO_CHAN(ftdmchan,"[CIC:%d]Starting native bridge with peer CIC %d\n",
sngss7_info->circuit->cic, peer_info->circuit->cic);
SS7_INFO_CHAN(ftdmchan,"[CIC:%d]Starting native bridge with peer CIC %d\n",
sngss7_info->circuit->cic, peer_info->circuit->cic);
/* make each one of us aware of the native bridge */
peer_info->peer_data = sngss7_info;
sngss7_info->peer_data = peer_info;
/* retrieve only first message from the others guys queue (must be IAM) */
event_clone = ftdm_queue_dequeue(peer_info->event_queue);
/* flush our own queue */
sngss7_flush_queue(sngss7_info->event_queue);
/* make each one of us aware of the native bridge */
peer_info->peer_data = sngss7_info;
sngss7_info->peer_data = peer_info;
/* Go to up until release comes, note that state processing is done different and much simpler when there is a peer,
We can't go to UP state right away yet though, so do not set the state to UP here, wait until the end of this function
because moving from one state to another causes the ftdmchan->usrmsg structure to be wiped
and we still need those variables for further IAM processing */
native_going_up = FTDM_TRUE;
}
/* Go to up until release comes, note that state processing is done different and much simpler when there is a peer,
We can't go to UP state right away yet though, so do not set the state to UP here, wait until the end of this function
because moving from one state to another causes the ftdmchan->usrmsg structure to be wiped
and we still need those variables for further IAM processing */
native_going_up = FTDM_TRUE;
}
}
}
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_NATIVE_SIGBRIDGE) && sngss7_info->peer_data) {
sngss7_span_data_t *span_data = ftdmchan->span->signal_data;
sngss7_event_data_t *event_clone = ftdm_queue_dequeue(sngss7_info->peer_data->event_queue);
/* Retrieve IAM from our peer */
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_NATIVE_SIGBRIDGE)) {
if (!event_clone) {
SS7_ERROR_CHAN(ftdmchan, "No event clone in peer queue!%s\n", "");
SS7_ERROR_CHAN(ftdmchan, "No IAM event clone in peer queue!%s\n", "");
} else if (event_clone->event_id != SNGSS7_CON_IND_EVENT) {
/* first message in the queue should ALWAYS be an IAM */
SS7_ERROR_CHAN(ftdmchan, "Invalid initial peer message type '%d'\n", event_clone->event_id);
@ -143,9 +139,6 @@ void ft_to_sngss7_iam (ftdm_channel_t * ftdmchan)
copy_ocn_to_sngss7(ftdmchan, &iam.origCdNum);
}
}
/* since this is the first time we dequeue an event from the peer, make sure our main thread process any other events,
this will trigger the interrupt in our span peer_chans queue which will wake up our main thread if it is sleeping */
ftdm_queue_enqueue(span_data->peer_chans, sngss7_info->peer_data->ftdmchan);
} else if (sngss7_info->circuit->transparent_iam &&
sngss7_retrieve_iam(ftdmchan, &iam) == FTDM_SUCCESS) {
SS7_INFO_CHAN(ftdmchan,"[CIC:%d]Tx IAM (Transparent)\n", sngss7_info->circuit->cic);
@ -236,7 +229,9 @@ void ft_to_sngss7_iam (ftdm_channel_t * ftdmchan)
the user sending FTDM_SIGEVENT_UP which can cause the application to misbehave (ie, no audio) */
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_UP);
ftdm_channel_advance_states(ftdmchan);
}
}
ftdm_safe_free(event_clone);
SS7_FUNC_TRACE_EXIT (__FUNCTION__);
return;