diff --git a/lib/config.js b/lib/config.js index 5fb1f05..2c04dfa 100644 --- a/lib/config.js +++ b/lib/config.js @@ -43,6 +43,7 @@ const OPTIONS_RESPONSE_REMOVE = process.env.OPTIONS_RESPONSE_REMOVE?.split(','). const REGISTER_RESPONSE_REMOVE = process.env.REGISTER_RESPONSE_REMOVE?.split(',').map(Number) || []; const JAMBONES_REGBOT_USER_AGENT = process.env.JAMBONES_REGBOT_USER_AGENT ; const JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL = process.env.JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL; +const JAMBONES_REGBOT_REGISTER_FAILURE_THRESHOLD = process.env.JAMBONES_REGBOT_REGISTER_FAILURE_THRESHOLD; module.exports = { JAMBONES_MYSQL_HOST, @@ -79,5 +80,6 @@ module.exports = { OPTIONS_RESPONSE_REMOVE, REGISTER_RESPONSE_REMOVE, JAMBONES_REGBOT_USER_AGENT, - JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL + JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL, + JAMBONES_REGBOT_REGISTER_FAILURE_THRESHOLD }; diff --git a/lib/regbot.js b/lib/regbot.js index e85e893..d4b022c 100644 --- a/lib/regbot.js +++ b/lib/regbot.js @@ -5,12 +5,14 @@ const { JAMBONES_REGBOT_CONTACT_USE_IP, REGISTER_RESPONSE_REMOVE, JAMBONES_REGBOT_USER_AGENT, - JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL + JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL, + JAMBONES_REGBOT_REGISTER_FAILURE_THRESHOLD } = require('./config'); const {isValidDomainOrIP, isValidIPv4} = require('./utils'); const DEFAULT_EXPIRES = (parseInt(JAMBONES_REGBOT_DEFAULT_EXPIRES_INTERVAL) || 3600); const MIN_EXPIRES = (parseInt(JAMBONES_REGBOT_MIN_EXPIRES_INTERVAL) || 30); const FAILURE_RETRY_INTERVAL = (parseInt(JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL) || 300); +const REGISTER_FAILURE_THRESHOLD = (parseInt(JAMBONES_REGBOT_REGISTER_FAILURE_THRESHOLD) || 3); const assert = require('assert'); const version = require('../package.json').version; const useragent = JAMBONES_REGBOT_USER_AGENT || `Jambonz ${version}`; @@ -44,6 +46,7 @@ class Regbot { this.from = `sip:${this.fromUser}@${fromDomain}`; this.aor = `${this.fromUser}@${this.sip_realm}`; this.status = 'none'; + this.consecutiveRemoveFailures = 0; } async start(srf) { @@ -149,18 +152,34 @@ class Regbot { this.logger.info(`${this.aor}: got ${res.status} registering to ${this.ipv4}:${this.port}`); this.timer = setTimeout(this.register.bind(this, srf), FAILURE_RETRY_INTERVAL * 1000); if (REGISTER_RESPONSE_REMOVE.includes(res.status)) { - const { updateCarrierBySid, lookupCarrierBySid } = srf.locals.dbHelpers; - await updateCarrierBySid(this.voip_carrier_sid, {requires_register: false}); - this.stop(srf); //Remove the retry timer - const carrier = await lookupCarrierBySid(this.voip_carrier_sid); - if (carrier) { - // eslint-disable-next-line max-len - this.logger.info(`Disabling Outbound Registration for carrier ${carrier.name} (sid:${carrier.voip_carrier_sid})`); - writeAlerts({ - account_sid: carrier.account_sid, - service_provider_sid: carrier.service_provider_sid, - message: `Disabling Outbound Registration for carrier ${carrier.name} (sid:${carrier.voip_carrier_sid})` - }); + this.consecutiveRemoveFailures++; + // eslint-disable-next-line max-len + this.logger.info(`${this.aor}: consecutive remove-failures: ${this.consecutiveRemoveFailures}/${REGISTER_FAILURE_THRESHOLD}`); + if (this.consecutiveRemoveFailures >= REGISTER_FAILURE_THRESHOLD) { + const { updateCarrierBySid, lookupCarrierBySid } = srf.locals.dbHelpers; + this.stop(srf); //Remove the retry timer + const carrier = await lookupCarrierBySid(this.voip_carrier_sid); + if (carrier && carrier.trunk_type != 'reg') { + await updateCarrierBySid(this.voip_carrier_sid, {requires_register: false}); + // eslint-disable-next-line max-len + this.logger.info(`Disabling Outbound Registration for carrier ${carrier.name} (sid:${carrier.voip_carrier_sid})`); + writeAlerts({ + account_sid: carrier.account_sid, + service_provider_sid: carrier.service_provider_sid, + // eslint-disable-next-line max-len + message: `Disabling Outbound Registration for carrier ${carrier.name} (sid:${carrier.voip_carrier_sid})` + }); + } else { + await updateCarrierBySid(this.voip_carrier_sid, {is_active: false}); + // eslint-disable-next-line max-len + this.logger.info(`Deactivating carrier ${carrier.name} (sid:${carrier.voip_carrier_sid}) due to registration errors`); + writeAlerts({ + account_sid: carrier.account_sid, + service_provider_sid: carrier.service_provider_sid, + // eslint-disable-next-line max-len + message: `Deactivating carrier ${carrier.name} (sid:${carrier.voip_carrier_sid}) due to registration errors` + }); + } } } expires = 0; @@ -170,6 +189,7 @@ class Regbot { // if there is a Contact header, it will use the expires value from there // otherwise, it will use the Expires header, acording to the SIP RFC 3261, section 10.2.4 Refreshing Bindings this.status = 'registered'; + this.consecutiveRemoveFailures = 0; expires = DEFAULT_EXPIRES; if (res.has('Expires')) { diff --git a/lib/sip-trunk-register.js b/lib/sip-trunk-register.js index c5ce347..2d6239f 100644 --- a/lib/sip-trunk-register.js +++ b/lib/sip-trunk-register.js @@ -241,6 +241,13 @@ const updateCarrierRegbots = async(logger, srf) => { Array.prototype.push.apply(gateways, gws.slice(i, i + 1000)); } + // preserve consecutive failure counts from existing regbots before stopping them + const failureCounts = new Map(); + regbots.forEach((rb) => { + const key = `${rb.voip_carrier_sid}:${rb.ipv4}:${rb.port}`; + failureCounts.set(key, rb.consecutiveRemoveFailures || 0); + }); + // stop / kill existing regbots regbots.forEach((rb) => rb.stop(srf)); regbots.length = 0; @@ -274,6 +281,11 @@ const updateCarrierRegbots = async(logger, srf) => { trunk_type: gw.carrier.trunk_type, sip_gateway_sid: gw.sip_gateway_sid }); + // restore consecutive failure count from previous regbot instance + const key = `${gw.carrier.voip_carrier_sid}:${gw.ipv4}:${gw.port}`; + if (failureCounts.has(key)) { + rb.consecutiveRemoveFailures = failureCounts.get(key); + } regbots.push(rb); rb.start(srf); batch_count++;