mirror of
https://github.com/jambonz/sbc-sip-sidecar.git
synced 2026-10-04 02:04:20 +00:00
get correct expiry for multi-contact registraitons (#149)
This commit is contained in:
+48
-3
@@ -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;
|
||||
|
||||
|
||||
@@ -149,6 +149,54 @@ test('configKey returns different strings when config differs', (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('getOwnContactExpires: picks our own binding when registrar returns multiple contacts', (t) => {
|
||||
const getOwnContactExpires = Regbot._getOwnContactExpires;
|
||||
|
||||
// reproduces EMF capture: AoR 9955 has another device registered against it (rinstance
|
||||
// contact with a counting-down expires). Our own contact is always granted 300.
|
||||
const contacts = [
|
||||
{uri: 'sip:9955@192.168.1.50:15447;rinstance=D4077321', params: {expires: '18', received: '"sip:192.168.100.186:15447"'}},
|
||||
{uri: 'sip:9955@example.com;transport=udp', params: {expires: '300'}}
|
||||
];
|
||||
t.equal(getOwnContactExpires(contacts, 'sip:9955@example.com'), 300,
|
||||
'returns our own binding expires (300), not the other device countdown (18)');
|
||||
|
||||
// order should not matter
|
||||
const reversed = [contacts[1], contacts[0]];
|
||||
t.equal(getOwnContactExpires(reversed, 'sip:9955@example.com'), 300,
|
||||
'matches our contact regardless of position in the header');
|
||||
|
||||
// host match is case-insensitive
|
||||
t.equal(getOwnContactExpires(contacts, 'sip:9955@EXAMPLE.COM'), 300,
|
||||
'host comparison is case-insensitive');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('getOwnContactExpires: returns undefined when our contact is absent', (t) => {
|
||||
const getOwnContactExpires = Regbot._getOwnContactExpires;
|
||||
const contacts = [
|
||||
{uri: 'sip:9955@192.168.1.50:15447;rinstance=D4077321', params: {expires: '18'}}
|
||||
];
|
||||
t.equal(getOwnContactExpires(contacts, 'sip:9955@example.com'), undefined,
|
||||
'no match -> undefined so caller can fall back to Expires header / single-contact');
|
||||
|
||||
t.equal(getOwnContactExpires([], 'sip:9955@example.com'), undefined,
|
||||
'empty contact list -> undefined');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('getOwnContactExpires: single matching contact returns its expires', (t) => {
|
||||
const getOwnContactExpires = Regbot._getOwnContactExpires;
|
||||
// AoR 25337 in the same capture: only our binding present
|
||||
const contacts = [
|
||||
{uri: 'sip:25337@example.com;transport=udp', params: {expires: '300'}}
|
||||
];
|
||||
t.equal(getOwnContactExpires(contacts, 'sip:25337@example.com'), 300,
|
||||
'single own binding resolves correctly');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('stopTimer clears timer without deleting gateways', (t) => {
|
||||
const rb = new Regbot(logger, {
|
||||
voip_carrier_sid: 'carrier-1',
|
||||
|
||||
Reference in New Issue
Block a user