get correct expiry for multi-contact registraitons (#149)

This commit is contained in:
Sam Machin
2026-08-20 17:33:23 -04:00
committed by GitHub
parent f7aaf58ebd
commit ba5ce5269d
2 changed files with 96 additions and 3 deletions
+48 -3
View File
@@ -9,6 +9,7 @@ const {
JAMBONES_REGBOT_REGISTER_FAILURE_THRESHOLD
} = require('./config');
const {isValidDomainOrIP, isValidIPv4} = require('./utils');
const parseUri = require('drachtio-srf').parseUri;
const DEFAULT_EXPIRES = (parseInt(JAMBONES_REGBOT_DEFAULT_EXPIRES_INTERVAL) || 3600);
const MIN_EXPIRES = (parseInt(JAMBONES_REGBOT_MIN_EXPIRES_INTERVAL) || 90);
const FAILURE_RETRY_INTERVAL = (parseInt(JAMBONES_REGBOT_FAILURE_RETRY_INTERVAL) || 300);
@@ -17,6 +18,38 @@ const assert = require('assert');
const version = require('../package.json').version;
const useragent = JAMBONES_REGBOT_USER_AGENT || `Jambonz ${version}`;
/**
* A 200 OK to a REGISTER may echo back every Contact bound to the AoR, not just ours
* (e.g. when another device is registered against the same account). Per RFC 3261 10.2.4
* we must refresh based on the expires of *our own* binding, so locate the returned Contact
* whose user and host match the Contact we sent.
* Returns the parsed expires (a number) of our binding, or undefined if it can't be matched.
*/
const getOwnContactExpires = (contacts, ownContactUri) => {
let own;
try {
own = parseUri(ownContactUri);
} catch {
return undefined;
}
if (!own || !own.host) return undefined;
const ownUser = own.user;
const ownHost = own.host.toLowerCase();
for (const c of contacts) {
let u;
try {
u = parseUri(c.uri);
} catch {
continue;
}
if (u && u.user === ownUser && (u.host || '').toLowerCase() === ownHost &&
c.params && c.params.expires !== undefined) {
return parseInt(c.params.expires);
}
}
return undefined;
};
class Regbot {
constructor(logger, opts) {
this.logger = logger;
@@ -235,9 +268,18 @@ class Regbot {
}
if (res.has('Contact')) {
const contact = res.getParsedHeader('Contact');
if (contact.length > 0 && contact[0].params && contact[0].params.expires) {
expires = parseInt(contact[0].params.expires);
const contacts = res.getParsedHeader('Contact');
const ownExpires = getOwnContactExpires(contacts, `${scheme}:${contactAddress}`);
if (typeof ownExpires === 'number' && !isNaN(ownExpires)) {
// refresh based on our own binding's expires
expires = ownExpires;
} else if (contacts.length === 1 && contacts[0].params && contacts[0].params.expires) {
// single binding returned: safe to use even if the registrar rewrote the contact host
expires = parseInt(contacts[0].params.expires);
} else if (contacts.length > 1) {
this.logger.info({aor: this.aor, ipv4: this.ipv4, port: this.port},
// eslint-disable-next-line max-len
`200 OK returned ${contacts.length} contacts but none matched our own binding; using expires ${expires}`);
}
} else {
this.logger.debug({ aor: this.aor, ipv4: this.ipv4, port: this.port },
@@ -355,5 +397,8 @@ const dnsResolverSrv = async(logger, hostname, transport) => {
};
// exposed for unit testing
Regbot._getOwnContactExpires = getOwnContactExpires;
module.exports = Regbot;