mirror of
https://github.com/jambonz/sbc-sip-sidecar.git
synced 2026-10-03 17:54:15 +00:00
The Call-ID of outbound registrations was the sip_gateway_sid, the same on every SBC. When the regbot role moved to the other SBC, the registrar saw a refresh of an existing binding from a different source address and Contact. Some registrars 200 such a refresh without updating their routing, so inbound calls to the registered trunk fail with 404 until the binding is recreated. The Call-ID is now sip_gateway_sid@<sending SBC public IP>: stable across refreshes and restarts of one SBC, new when the role moves, so a move looks like a new registration. register_status also records the sending SBC as sbcAddress. With AWS_LIFECYCLE_DRAIN enabled the sidecar polls IMDS autoscaling/target-lifecycle-state (the signal inbound drains on; detection only, inbound completes the lifecycle hook). When the instance is being scaled in, the regbot holder releases the lease while still running instead of after the instance is gone; until now the draining SBC kept the registrations, so carriers kept sending registration-trunk calls to an SBC that answers new INVITEs with 503. It never claims the role back. Once another SBC has claimed it, the draining SBC un-REGISTERs (Expires: 0) the bindings whose Contact carries its own IP. Bindings with an AoR or realm Contact are left alone: the new SBC sends the same Contact, and its REGISTER has already replaced ours. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
1.8 KiB
JavaScript
45 lines
1.8 KiB
JavaScript
/* Detect that this instance is being scaled in by polling IMDS autoscaling/target-lifecycle-state,
|
|
* the same signal sbc-inbound drains on: it reads 'Terminated' once the Auto Scaling group moves
|
|
* the instance to Terminating:Wait. Detection only -- sbc-inbound owns the lifecycle hook and
|
|
* completes it once calls have drained, so this needs no Auto Scaling API access.
|
|
*
|
|
* IMDSv2 only: fetch a session token with PUT, then present it on the GET. */
|
|
const IMDS = 'http://169.254.169.254/latest';
|
|
const IMDS_TIMEOUT_MS = 2000;
|
|
const POLL_INTERVAL_MS = 20000;
|
|
|
|
const imds = async(path) => {
|
|
const tokenRes = await fetch(`${IMDS}/api/token`, {
|
|
method: 'PUT',
|
|
headers: {'X-aws-ec2-metadata-token-ttl-seconds': '60'},
|
|
signal: AbortSignal.timeout(IMDS_TIMEOUT_MS)
|
|
});
|
|
if (!tokenRes.ok) throw new Error(`IMDS token request failed: ${tokenRes.status}`);
|
|
const token = await tokenRes.text();
|
|
const res = await fetch(`${IMDS}/meta-data/${path}`, {
|
|
headers: {'X-aws-ec2-metadata-token': token},
|
|
signal: AbortSignal.timeout(IMDS_TIMEOUT_MS)
|
|
});
|
|
if (!res.ok) throw new Error(`IMDS ${path} request failed: ${res.status}`);
|
|
return res.text();
|
|
};
|
|
|
|
module.exports = (logger, onScaleIn, {interval = POLL_INTERVAL_MS} = {}) => {
|
|
let fired = false;
|
|
const timer = setInterval(async() => {
|
|
try {
|
|
const state = await imds('autoscaling/target-lifecycle-state');
|
|
if (state !== 'Terminated' || fired) return;
|
|
fired = true;
|
|
clearInterval(timer);
|
|
logger.info('AWS scale-in detected (target-lifecycle-state is Terminated)');
|
|
onScaleIn();
|
|
} catch (err) {
|
|
logger.warn({err}, 'Error polling IMDS autoscaling/target-lifecycle-state');
|
|
}
|
|
}, interval);
|
|
timer.unref();
|
|
logger.info('AWS lifecycle drain enabled: polling IMDS autoscaling/target-lifecycle-state');
|
|
return timer;
|
|
};
|