drachtio-srf 5.0.20 → 5.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/digest-client.js +13 -6
- package/lib/drachtio-agent.js +31 -4
- package/package.json +1 -1
- package/test/unit-tests/digest-client.js +136 -0
package/lib/digest-client.js
CHANGED
|
@@ -110,13 +110,20 @@ module.exports = class DigestClient {
|
|
|
110
110
|
|
|
111
111
|
options.headers = headers;
|
|
112
112
|
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
113
|
+
// After a 401/407, pin the credentialled retry to the server that
|
|
114
|
+
// challenged us. Skip only when the effective next-hop is already
|
|
115
|
+
// an IP literal -- a hostname proxy re-resolves on retry and can
|
|
116
|
+
// land on a different A-record. The proxy (when set) is the actual
|
|
117
|
+
// next-hop; the request-uri only determines routing when no proxy
|
|
118
|
+
// is configured.
|
|
116
119
|
const originalUri = options.uri;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
const IPV4_IN_SIP = /sips?:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
|
|
121
|
+
const proxyIsIpLiteral = options.proxy && IPV4_IN_SIP.test(options.proxy);
|
|
122
|
+
const uriIsIpLiteral = IPV4_IN_SIP.test(originalUri);
|
|
123
|
+
const nextHopIsIpLiteral = options.proxy ? proxyIsIpLiteral : uriIsIpLiteral;
|
|
124
|
+
|
|
125
|
+
if (!nextHopIsIpLiteral) {
|
|
126
|
+
const transport = parseTransportToken(options.proxy) || parseTransportToken(originalUri);
|
|
120
127
|
let proxy = `sip:${this.res.source_address}:${this.res.source_port}`;
|
|
121
128
|
if (transport) proxy += `;transport=${transport}`;
|
|
122
129
|
Object.assign(options, {proxy});
|
package/lib/drachtio-agent.js
CHANGED
|
@@ -301,7 +301,14 @@ class DrachtioAgent extends Emitter {
|
|
|
301
301
|
transactionId: transactionId
|
|
302
302
|
};
|
|
303
303
|
|
|
304
|
-
|
|
304
|
+
let sipMsg;
|
|
305
|
+
try {
|
|
306
|
+
sipMsg = new SipMessage(msg);
|
|
307
|
+
} catch(err) {
|
|
308
|
+
console.error(err, `unable to parse echoed sent request: ${msg}`);
|
|
309
|
+
return params.callback(err);
|
|
310
|
+
}
|
|
311
|
+
const req = new Request(sipMsg, meta);
|
|
305
312
|
req.agent = this;
|
|
306
313
|
req.socket = obj.socket;
|
|
307
314
|
if (params.options.auth) {
|
|
@@ -356,7 +363,14 @@ class DrachtioAgent extends Emitter {
|
|
|
356
363
|
obj.pendingRequests.set(msgId, (token, msg, meta) => {
|
|
357
364
|
obj.pendingRequests.delete(msgId);
|
|
358
365
|
if ('OK' !== token[0]) { if (callback) return callback(token[1]); return; }
|
|
359
|
-
|
|
366
|
+
let responseMsg;
|
|
367
|
+
try {
|
|
368
|
+
responseMsg = new SipMessage(msg);
|
|
369
|
+
} catch(err) {
|
|
370
|
+
console.error(err, `unable to parse echoed sent response: ${msg}`);
|
|
371
|
+
if (callback) callback(err);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
360
374
|
res.meta = meta;
|
|
361
375
|
if (callback) {
|
|
362
376
|
callback(null, responseMsg);
|
|
@@ -401,7 +415,14 @@ class DrachtioAgent extends Emitter {
|
|
|
401
415
|
if ('OK' !== token[0]) {
|
|
402
416
|
return callback(token[1]);
|
|
403
417
|
}
|
|
404
|
-
|
|
418
|
+
let sipMsg;
|
|
419
|
+
try {
|
|
420
|
+
sipMsg = new SipMessage(msg);
|
|
421
|
+
} catch(err) {
|
|
422
|
+
console.error(err, `unable to parse echoed sent ACK: ${msg}`);
|
|
423
|
+
return callback(err);
|
|
424
|
+
}
|
|
425
|
+
callback(null, sipMsg);
|
|
405
426
|
});
|
|
406
427
|
}
|
|
407
428
|
}
|
|
@@ -808,7 +829,13 @@ class DrachtioAgent extends Emitter {
|
|
|
808
829
|
const msgSource = token[2];
|
|
809
830
|
const msgTime = token[3];
|
|
810
831
|
rawMsg = msg.slice(pos + 2);
|
|
811
|
-
|
|
832
|
+
let cdrSipMsg;
|
|
833
|
+
try {
|
|
834
|
+
cdrSipMsg = new SipMessage(rawMsg);
|
|
835
|
+
} catch(err) {
|
|
836
|
+
console.error(err, `unable to parse CDR sip message: ${rawMsg}`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
812
839
|
const args = [msgSource, msgTime];
|
|
813
840
|
if (cdrEvent !== 'attempt') { args.push(token[4]); }
|
|
814
841
|
args.push(cdrSipMsg);
|
package/package.json
CHANGED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require('assert');
|
|
2
|
+
require('mocha');
|
|
3
|
+
require('should');
|
|
4
|
+
|
|
5
|
+
const DigestClient = require('../../lib/digest-client');
|
|
6
|
+
|
|
7
|
+
function buildRes({uri, proxy, statusCode = 401, sourceAddress = '1.1.1.1', sourcePort = 5060}) {
|
|
8
|
+
const challengeHeader = statusCode === 407 ? 'proxy-authenticate' : 'www-authenticate';
|
|
9
|
+
const captured = {};
|
|
10
|
+
|
|
11
|
+
const options = {
|
|
12
|
+
method: 'INVITE',
|
|
13
|
+
uri,
|
|
14
|
+
auth: {username: 'u', password: 'p'},
|
|
15
|
+
headers: {}
|
|
16
|
+
};
|
|
17
|
+
if (proxy) options.proxy = proxy;
|
|
18
|
+
|
|
19
|
+
const req = {
|
|
20
|
+
method: 'INVITE',
|
|
21
|
+
_originalParams: {options},
|
|
22
|
+
get: (h) => {
|
|
23
|
+
const lower = h.toLowerCase();
|
|
24
|
+
if (lower === 'call-id') return 'call-id-1';
|
|
25
|
+
if (lower === 'from') return '<sip:u@localhost>;tag=abc';
|
|
26
|
+
return undefined;
|
|
27
|
+
},
|
|
28
|
+
getParsedHeader: (h) => {
|
|
29
|
+
if (h === 'cseq') return {seq: 1, method: 'INVITE'};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const agent = {
|
|
34
|
+
request: (opts) => { captured.options = opts; }
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const res = {
|
|
38
|
+
statusCode,
|
|
39
|
+
source_address: sourceAddress,
|
|
40
|
+
source_port: sourcePort,
|
|
41
|
+
socket: {},
|
|
42
|
+
req,
|
|
43
|
+
agent,
|
|
44
|
+
has: (h) => h.toLowerCase() === challengeHeader,
|
|
45
|
+
get: (h) => h.toLowerCase() === challengeHeader
|
|
46
|
+
? 'Digest realm="test",nonce="abc123",qop="auth"'
|
|
47
|
+
: undefined
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return {res, captured};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('DigestClient proxy pinning after challenge', function() {
|
|
54
|
+
|
|
55
|
+
it('pins to challenger when no proxy is set and uri is a hostname', function() {
|
|
56
|
+
const {res, captured} = buildRes({
|
|
57
|
+
uri: 'sip:user@sbc.example.com',
|
|
58
|
+
statusCode: 401,
|
|
59
|
+
sourceAddress: '1.1.1.1',
|
|
60
|
+
sourcePort: 5060
|
|
61
|
+
});
|
|
62
|
+
new DigestClient(res).authenticate(() => {});
|
|
63
|
+
captured.options.proxy.should.eql('sip:1.1.1.1:5060');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('does not pin when no proxy is set and uri is an IPv4 literal', function() {
|
|
67
|
+
const {res, captured} = buildRes({
|
|
68
|
+
uri: 'sip:1.2.3.4:5060',
|
|
69
|
+
statusCode: 401
|
|
70
|
+
});
|
|
71
|
+
new DigestClient(res).authenticate(() => {});
|
|
72
|
+
(captured.options.proxy === undefined).should.be.true();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('does not pin when proxy is already an IPv4 literal (407)', function() {
|
|
76
|
+
const {res, captured} = buildRes({
|
|
77
|
+
uri: 'sip:user@sbc.example.com',
|
|
78
|
+
proxy: 'sip:9.9.9.9:5080',
|
|
79
|
+
statusCode: 407,
|
|
80
|
+
sourceAddress: '1.1.1.1',
|
|
81
|
+
sourcePort: 5060
|
|
82
|
+
});
|
|
83
|
+
new DigestClient(res).authenticate(() => {});
|
|
84
|
+
captured.options.proxy.should.eql('sip:9.9.9.9:5080');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('pins to challenger when proxy is a hostname (407)', function() {
|
|
88
|
+
const {res, captured} = buildRes({
|
|
89
|
+
uri: 'sip:user@host',
|
|
90
|
+
proxy: 'sip:sbc.example.com',
|
|
91
|
+
statusCode: 407,
|
|
92
|
+
sourceAddress: '1.1.1.1',
|
|
93
|
+
sourcePort: 5060
|
|
94
|
+
});
|
|
95
|
+
new DigestClient(res).authenticate(() => {});
|
|
96
|
+
captured.options.proxy.should.eql('sip:1.1.1.1:5060');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('pins and preserves transport taken from hostname proxy', function() {
|
|
100
|
+
const {res, captured} = buildRes({
|
|
101
|
+
uri: 'sip:user@host',
|
|
102
|
+
proxy: 'sip:sbc.example.com;transport=udp',
|
|
103
|
+
statusCode: 407,
|
|
104
|
+
sourceAddress: '1.1.1.1',
|
|
105
|
+
sourcePort: 5061
|
|
106
|
+
});
|
|
107
|
+
new DigestClient(res).authenticate(() => {});
|
|
108
|
+
captured.options.proxy.should.eql('sip:1.1.1.1:5061;transport=udp');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('pins and preserves transport taken from uri when no proxy is set', function() {
|
|
112
|
+
const {res, captured} = buildRes({
|
|
113
|
+
uri: 'sip:user@sbc.example.com;transport=udp',
|
|
114
|
+
statusCode: 401,
|
|
115
|
+
sourceAddress: '1.1.1.1',
|
|
116
|
+
sourcePort: 5060
|
|
117
|
+
});
|
|
118
|
+
new DigestClient(res).authenticate(() => {});
|
|
119
|
+
captured.options.proxy.should.eql('sip:1.1.1.1:5060;transport=udp');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('pins when proxy is a hostname even if uri is an IPv4 literal', function() {
|
|
123
|
+
// proxy is the actual next-hop; an IP literal in the uri does not
|
|
124
|
+
// prevent the hostname proxy from re-resolving to a different A-record.
|
|
125
|
+
const {res, captured} = buildRes({
|
|
126
|
+
uri: 'sip:user@1.2.3.4',
|
|
127
|
+
proxy: 'sip:sbc.example.com',
|
|
128
|
+
statusCode: 407,
|
|
129
|
+
sourceAddress: '1.1.1.1',
|
|
130
|
+
sourcePort: 5060
|
|
131
|
+
});
|
|
132
|
+
new DigestClient(res).authenticate(() => {});
|
|
133
|
+
captured.options.proxy.should.eql('sip:1.1.1.1:5060');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
});
|