Skip to content

Commit 67fe8a4

Browse files
committed
app_ccsa: Avoid queuing in absurd scenarios.
The CCSA() application previously happily queued in many circumstances that were rather inappropriate for either off-hook and/or callback queuing, in particular when a destination couldn't be reached using any routes and CCSA() would immediately offhook queue on a route. Furthermore, since CCSA only pops items out of the queue for call completion when a current call finishes, if there are no calls using a route, queuing is pointless. Add additional checks to avoid queuing altogether in these scenarios and others like them.
1 parent 3f87130 commit 67fe8a4

File tree

1 file changed

+171
-21
lines changed

1 file changed

+171
-21
lines changed

apps/app_ccsa.c

Lines changed: 171 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include "asterisk/strings.h"
5353
#include "asterisk/musiconhold.h"
5454
#include "asterisk/format_cache.h"
55+
#include "asterisk/devicestate.h"
5556

5657
/*** DOCUMENTATION
5758
<application name="CCSA" language="en_US">
@@ -135,6 +136,9 @@
135136
<value name="NO_ROUTES">
136137
No routes available for CCSA destination
137138
</value>
139+
<value name="UNROUTABLE">
140+
Destination cannot be reached using any routes
141+
</value>
138142
<value name="UNAUTHORIZED">
139143
Not authorized for the available CCSA routes
140144
</value>
@@ -276,6 +280,7 @@
276280
<synopsis>Threshold of queued priority 3 calls at which it is unlikely an additional call would be able to queue successfully at priority 3 without timing out.</synopsis>
277281
<description>
278282
<para>If this threshold of queued priority 3 calls is exceeded, additional calls will not be allowed to Off Hook Queue until queued priority 3 calls drop below this threshold again.</para>
283+
<para>To completely disable queuing against this route, specify a negative value (e.g. <literal>-1</literal>). This is useful when dealing with virtual routes against which queuing does not make sense, such as IP-based (e.g. SIP) trunks.</para>
279284
</description>
280285
</configOption>
281286
<configOption name="limit" default="0">
@@ -312,6 +317,12 @@
312317
<para>By default, when set to no, BUSY is treated as a successful call outcome, and alternate routing will stop at this point (appropriate for IP trunking). For analog trunking, you will probably want to set this to yes, so that if BUSY is received, alternate routing will continue using other routes.</para>
313318
</description>
314319
</configOption>
320+
<configOption name="devstate">
321+
<synopsis>Aggregate device state for trunk group</synopsis>
322+
<description>
323+
<para>If specified, a list of devices whose aggregate device state will be used to determine the current status of the trunk group (whether idle or in use).</para>
324+
</description>
325+
</configOption>
315326
<configOption name="time">
316327
<synopsis>Time restrictions for route</synopsis>
317328
<description>
@@ -468,6 +479,7 @@ struct route {
468479
char facility[AST_MAX_CONTEXT]; /*!< Facility Name */
469480
enum facility_type factype; /*!< Facility Type */
470481
char dialstr[PATH_MAX]; /*!< Dial string */
482+
char *devstate; /*!< Device state */
471483
unsigned int threshold; /*!< Threshold at which facility is "saturated" */
472484
unsigned int limit; /*!< Concurrent call limit */
473485
unsigned int frl:3; /*!< Minimum Facility Restriction Level required */
@@ -956,6 +968,22 @@ static int route_num_priority3_calls(const char *route)
956968
return pri3calls;
957969
}
958970

971+
static int route_has_any_calls(const char *route)
972+
{
973+
struct ccsa_call *call;
974+
975+
AST_RWLIST_RDLOCK(&calls);
976+
AST_LIST_TRAVERSE(&calls, call, entry) {
977+
if (!strcmp(route, call->route)) {
978+
AST_RWLIST_UNLOCK(&calls);
979+
return 1;
980+
}
981+
}
982+
AST_RWLIST_UNLOCK(&calls);
983+
984+
return 0;
985+
}
986+
959987
static int cbq_calls_pending(const char *caller)
960988
{
961989
struct ccsa_call *call;
@@ -1079,35 +1107,138 @@ static int store_dtmf(struct ast_channel *chan)
10791107
return 0;
10801108
}
10811109

1082-
static int route_permits_ohq(const char *route)
1110+
static int route_permits_ohq_locked(struct route *f, time_t elapsed, int is_simulation)
10831111
{
1084-
int threshold = 0;
1085-
struct route *f;
10861112
/* Nortel 553-2751-101 4.00, Off-Hook Queuing availability:
10871113
* Each trunk route has a threshold value that indicates the maximum number of priority 3 calls
10881114
* that can be queued against it before OHQ timeout becomes a high probability.
10891115
* Before a call is placed in the OHQ, the current queue count is compared with the
10901116
* threshold value for each eligible trunk route in the initial set of routes.
10911117
* If at least one of the trunk routes has a count less than or equal to the threshold value,
10921118
* the call is allowed to perform OHQ against all OHQ eligible routes. */
1093-
int pri3calls = route_num_priority3_calls(route);
1094-
AST_RWLIST_RDLOCK(&routes);
1095-
f = find_route(route, 0);
1096-
threshold = f ? f->threshold : 0;
1097-
if (!ast_strlen_zero(f->time) && time_matches(f->time)) {
1098-
f = NULL; /* Route time restrictions not satisfied, so OHQ not permitted either */
1119+
int pri3calls;
1120+
1121+
/* XXX If the route currently has no calls, it may not make sense to queue.
1122+
* For example, if we tried route 0 and the call failed, but route 0 was
1123+
* using a trunk group with 0 calls, then there is no point in off-hook
1124+
* queuing on route 0, since it wasn't in use in the first place,
1125+
* so off-hooking queuing on it won't accomplish anything.
1126+
*
1127+
* Note that routes do not correspond 1:1 to trunk groups,
1128+
* so to determine if the underlying trunk group is currently saturated or not,
1129+
* we need to be able to determine the "device state" of the trunk group. */
1130+
if (!f->limit) {
1131+
/* If there is no limit, then this is a "virtual" trunk group, essentially,
1132+
* i.e. there is no hard cap on allowed calls. Common for enterprise SIP trunking. */
1133+
ast_debug(6, "Facility %s does not have a defined capacity, won't queue against it\n", f->name);
1134+
return 0;
10991135
}
1100-
AST_RWLIST_UNLOCK(&routes);
1101-
/* Would've already thrown a warning about this route not existing if it didn't, so don't do it again. */
1102-
if (!f) {
1136+
1137+
if (f->threshold < 0) {
1138+
/* Queuing explicitly disabled */
1139+
ast_debug(6, "Facility %s queuing disabled\n", f->name);
11031140
return 0;
11041141
}
1105-
if (pri3calls > threshold) {
1142+
1143+
if (!ast_strlen_zero(f->time) && time_matches(f->time)) {
1144+
ast_debug(6, "Facility %s skipped due to time restrictions\n", f->name);
1145+
return 0; /* Route time restrictions not satisfied, so OHQ not permitted either */
1146+
}
1147+
1148+
pri3calls = route_num_priority3_calls(f->name);
1149+
if (pri3calls > f->threshold) {
1150+
ast_debug(6, "Facility %s skipped (too many priority 3 calls queued: %d)\n", f->name, pri3calls);
11061151
return 0; /* So many queued calls already that it's unlikely OHQ call will succeed in queue before timing out */
11071152
}
1153+
1154+
/* Only check current call status if not in simulation */
1155+
if (!is_simulation) {
1156+
if (f->devstate) {
1157+
/* Even if there is a call limit, like for analog trunk groups with a fixed number of circuits,
1158+
* if the entire trunk group is idle, it is still inappropriate to queue against it,
1159+
* since the trunk group isn't currently saturated.
1160+
*
1161+
* However, there is no way to automatically determine ourselves if the trunk group is completely
1162+
* saturated in this case. Multiple routes could use certain circuits, and those circuits could
1163+
* also be used outside of this module, so we can't just calculate the device state.
1164+
* In this case, it would make sense to lookup an "aggregate device state" for the entire trunk group,
1165+
* and only if that comes back as "all trunks in the trunk group are busy" would queuing make sense.
1166+
* Conversely, if no trunks are busy, then queuing also doesn't make sense. */
1167+
enum ast_device_state devstate = ast_device_state(f->devstate);
1168+
switch (devstate) {
1169+
case AST_DEVICE_NOT_INUSE:
1170+
/* The entire trunk group is currently "idle",
1171+
* so if a previous attempt to use this route just now failed,
1172+
* why try it again?
1173+
* It's important that we bound this based on the time to do the
1174+
* first pass of all the routes. Otherwise, a facility might have
1175+
* been busy on the first attempt but might be free now.
1176+
*
1177+
* XXX A more reliable way to do this would be keep track
1178+
* of the device state associated with each route as we attempt using
1179+
* it the first time. If it was NOTINUSE, then that means we should
1180+
* NOT try it the second pass, no matter what.
1181+
* The logic here just assumes if not very much time has elapsed since
1182+
* the first pass, the state now will be whatever it was a moment ago,
1183+
* but technically it's the older state that we want here.
1184+
* Because some calls could technically still clear in the same second,
1185+
* there is a slight chance some calls could've queued that will now be excluded.
1186+
* Beyond 1 second, the chance of a race condition is too great to do this.*/
1187+
if (elapsed < 1) {
1188+
ast_debug(6, "Facility %s skipped (trunk group isn't in use now and probably wasn't on last attempt)\n", f->name);
1189+
return 0;
1190+
}
1191+
/* Fall through */
1192+
default:
1193+
/* Okay to queue against, or at least, these don't preclude queuing */
1194+
break;
1195+
}
1196+
}
1197+
1198+
if (!route_has_any_calls(f->name)) {
1199+
/* The only way that off-hook queuing will end successfully is if there is a call currently
1200+
* in the queue that ends, which will write to the alertpipe and wake up a call
1201+
* to go ahead and try again.
1202+
* If there aren't any calls, even if the trunk group has capacity, there isn't anything
1203+
* to wake us up, so it won't work anyways, so don't queue for no reason.
1204+
*
1205+
* XXX In the future, the option could be added to "poll" the device state periodically to retry
1206+
* the call attempt, which would better handle facilities used outside of this application. */
1207+
ast_debug(6, "Facility %s skipped (doesn't have any calls currently, so nothing to wait for)\n", f->name);
1208+
return 0;
1209+
}
1210+
}
1211+
11081212
return 1;
11091213
}
11101214

1215+
static int route_permits_ohq(const char *route, time_t elapsed, int is_simulation)
1216+
{
1217+
struct route *f;
1218+
int can_queue;
1219+
1220+
AST_RWLIST_RDLOCK(&routes);
1221+
f = find_route(route, 0);
1222+
if (!f) {
1223+
/* Would've already thrown a warning about this route not existing if it didn't, so don't do it again. */
1224+
can_queue = 0;
1225+
} else {
1226+
can_queue = route_permits_ohq_locked(f, elapsed, is_simulation);
1227+
}
1228+
AST_RWLIST_UNLOCK(&routes);
1229+
1230+
return can_queue;
1231+
}
1232+
1233+
static int route_permits_cbq(const char *route)
1234+
{
1235+
int permitted;
1236+
AST_RWLIST_RDLOCK(&routes);
1237+
permitted = route_has_any_calls(route);
1238+
AST_RWLIST_UNLOCK(&routes);
1239+
return permitted;
1240+
}
1241+
11111242
static int off_hook_queue(struct ast_channel *chan, struct ccsa_call *call, int ohq)
11121243
{
11131244
int timeout, ms, remaining_time;
@@ -1313,13 +1444,9 @@ static void *call_back_queue(void *data)
13131444
int advanced = 0;
13141445

13151446
newroute = ast_strdup(call->nextroute);
1316-
if (!newroute) {
1317-
ast_log(LOG_WARNING, "strdup failed\n");
1318-
} else {
1447+
if (newroute) {
13191448
newfacility = ast_strdup(next_facility);
1320-
if (!newfacility) {
1321-
ast_log(LOG_WARNING, "strdup failed\n");
1322-
} else {
1449+
if (newfacility) {
13231450
AST_RWLIST_WRLOCK(&calls);
13241451
/* Make sure we're still in the list, too. */
13251452
AST_LIST_TRAVERSE(&calls, ecall, entry) {
@@ -1740,6 +1867,7 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
17401867
char *auth_sub_context = NULL;
17411868
char *callback_caller_context = NULL, *callback_dest_context = NULL;
17421869
unsigned int queue_promo_timer, route_advance_timer;
1870+
time_t start;
17431871

17441872
AST_RWLIST_RDLOCK(&ccsas);
17451873
c = find_ccsa(ccsa, 0);
@@ -1831,6 +1959,7 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
18311959

18321960
ast_assert(chan || fd >= 0);
18331961
ccsa_log(chan, fd, "Beginning CCSA call\n");
1962+
start = time(NULL);
18341963

18351964
routes = ast_strdupa(faclist);
18361965
while ((route = strsep(&routes, "|"))) {
@@ -1890,7 +2019,6 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
18902019
ast_log(LOG_WARNING, "Invalid MLPP preemption capability: %c\n", isprint(preempt) ? preempt : '?');
18912020
} else {
18922021
/* Now, try preempting an existing call before we queue. */
1893-
18942022
try_preempt = preempt;
18952023
ccsa_log(chan, fd, "Trying to preempt calls < '%c'\n", preempt);
18962024
routes = ast_strdupa(faclist);
@@ -1948,6 +2076,7 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
19482076

19492077
if (ohq) { /* Off Hook Queue takes precedence over Call Back Queue, if permitted: anyone can OHQ, unlike CBQ */
19502078
int eligible = 0;
2079+
time_t elapsed;
19512080
/* Start off by trying to off hook queue on non-MER routes */
19522081
/* To be eligible for OHQ, at least 1 trunk route must have a call count <= threshold,
19532082
* then all routes are eligible for OHQ. */
@@ -1956,8 +2085,9 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
19562085
have_mer = 0;
19572086

19582087
routes = ast_strdupa(faclist);
2088+
elapsed = time(NULL) - start;
19592089
while ((route = strsep(&routes, "|"))) {
1960-
if (route_permits_ohq(route)) {
2090+
if (route_permits_ohq(route, elapsed, fd != -1)) {
19612091
eligible = 1;
19622092
break; /* No need to check any more */
19632093
}
@@ -2101,6 +2231,21 @@ static int ccsa_run(struct ast_channel *chan, int fd, const char *exten, const c
21012231
* Our motto is "the BSPs are always right" :)
21022232
*/
21032233

2234+
if (fd == -1) {
2235+
/* If not simulation, ensure we can actually CBQ */
2236+
routes = ast_strdupa(faclist);
2237+
while ((route = strsep(&routes, "|"))) {
2238+
if (route_permits_cbq(route)) {
2239+
break; /* No need to check any more */
2240+
}
2241+
}
2242+
if (!route) {
2243+
ast_debug(3, "Call ineligible for Call Back Queue\n");
2244+
ccsa_set_result_val(chan, "UNROUTABLE");
2245+
return 0;
2246+
}
2247+
}
2248+
21042249
if (!chan) {
21052250
ccsa_log(chan, fd, "Call Back Queue treatment\n");
21062251
return 0;
@@ -2888,6 +3033,8 @@ static int ccsa_reload(int reload)
28883033
f->threshold = atoi(var->value);
28893034
} else if (!strcasecmp(var->name, "limit") && !ast_strlen_zero(var->value)) {
28903035
f->limit = atoi(var->value);
3036+
} else if (!strcasecmp(var->name, "devstate")) {
3037+
f->devstate = ast_strdup(var->value);
28913038
} else {
28923039
ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s at line %d of %s\n", var->name, var->name, var->lineno, CONFIG_FILE);
28933040
}
@@ -3003,6 +3150,9 @@ static int unload_module(void)
30033150

30043151
AST_RWLIST_WRLOCK(&routes);
30053152
while ((f = AST_RWLIST_REMOVE_HEAD(&routes, entry))) {
3153+
if (f->devstate) {
3154+
ast_free(f->devstate);
3155+
}
30063156
ast_free(f);
30073157
}
30083158
AST_RWLIST_UNLOCK(&routes);

0 commit comments

Comments
 (0)