diff --git a/lib/call-session.js b/lib/call-session.js index a101eab..90f2269 100644 --- a/lib/call-session.js +++ b/lib/call-session.js @@ -19,6 +19,9 @@ const debug = require('debug')('jambonz:sbc-inbound'); const MS_TEAMS_USER_AGENT = 'Microsoft.PSTNHub.SIPProxy'; const MS_TEAMS_SIP_ENDPOINT = 'sip.pstnhub.microsoft.com'; const IMMUTABLE_HEADERS = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', 'content-length']; +const NONCOPYABLE_RESPONSE_HEADERS = [ + 'via', 'from', 'to', 'call-id', 'cseq', 'contact', 'content-length', 'content-type' +]; /** * this is to make sure the outgoing From has the number in the incoming From @@ -281,6 +284,7 @@ class CallSession extends Emitter { proxy, headers, responseHeaders, + passFailure: false, proxyRequestHeaders: [ 'all', '-Authorization', @@ -355,12 +359,16 @@ class CallSession extends Emitter { this.stats.increment('sbc.terminations', tags); this.logger.info(`call failed to connect to feature server with ${err.status}`); - /* capture trace_id from the feature server's error response for the CDR */ + /* capture trace_id and application_sid from the feature server's error response for the CDR. */ if (this.req.locals.cdr && err.res) { const trace_id = err.res.get('X-Trace-ID'); if (trace_id) { this.req.locals.cdr.trace_id = trace_id; } + const application_sid = err.res.get('X-Application-Sid'); + if (application_sid) { + this.req.locals.cdr.application_sid = application_sid; + } } this.emit('failed'); @@ -377,6 +385,22 @@ class CallSession extends Emitter { .catch((err) => this.logger.error(err, 'Error decrementing call counts')); } + + /* manually proxy the failure response to UAS so that the trace_id/application_sid capture above runs before + res.end fires (which is what triggers the failure-CDR writer). */ + if (err.message !== 'call canceled' && !this.res.finalResponseSent) { + if (err instanceof SipError && err.res) { + const headers = {}; + Object.keys(err.res.headers || {}).forEach((h) => { + if (!NONCOPYABLE_RESPONSE_HEADERS.includes(h)) headers[h] = err.res.headers[h]; + }); + this.res.send(err.status, err.reason, {headers}); + } + else { + this.res.send(err.status || 500, err.reason); + } + } + this.srf.endSession(this.req); } } diff --git a/lib/middleware.js b/lib/middleware.js index 25c982e..f397077 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -31,7 +31,6 @@ module.exports = function(srf, logger) { queryCallLimits, } = srf.locals.dbHelpers; const {stats, writeCdrs, lookupAuthCarriersForAccountAndSP, getApplicationForDidAndCarrier} = srf.locals; - const {retrieveKey} = srf.locals.realtimeDbHelpers; const initLocals = (req, res, next) => { const callId = req.get('Call-ID'); @@ -60,32 +59,16 @@ module.exports = function(srf, logger) { /* write cdr for non-success response here */ res.once('end', ({status}) => { - if (req.locals.cdr && req.locals.cdr.account_sid && status > 200 && 401 !== status) { - const writeCdr = () => { - const trunk = ['trunk', 'teams'].includes(req.locals.originator) ? - req.locals.carrier : req.locals.originator; - writeCdrs({...req.locals.cdr, - terminated_at: Date.now(), - termination_reason: status === 487 === status ? 'caller abandoned' : 'failed', - sip_status: status, - trunk, - ...(req.locals.application_sid && {application_sid: req.locals.application_sid}) - }).catch((err) => logger.error({err}, 'Error writing cdr for call failure')); - }; - - /* if trace_id is missing, look it up from Redis using the original Call-ID */ - if (!req.locals.cdr.trace_id) { - retrieveKey(`callid:${callId}`) - .then((trace_id) => { - if (trace_id) req.locals.cdr.trace_id = trace_id; - return; - }) - .catch((err) => logger.debug({err, callId}, 'Error retrieving trace_id from Redis')) - .finally(() => writeCdr()) - .catch(() => {}); - } else { - writeCdr(); - } + if (req.locals.cdr && req.locals.cdr.account_sid && status > 200 && 401 !== status) { + const trunk = ['trunk', 'teams'].includes(req.locals.originator) ? + req.locals.carrier : req.locals.originator; + writeCdrs({...req.locals.cdr, + terminated_at: Date.now(), + termination_reason: status === 487 ? 'caller abandoned' : 'failed', + sip_status: status, + trunk, + ...(req.locals.application_sid && {application_sid: req.locals.application_sid}) + }).catch((err) => logger.error({err}, 'Error writing cdr for call failure')); } });