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.
@@ -110,13 +110,20 @@ module.exports = class DigestClient {
110
110
 
111
111
  options.headers = headers;
112
112
 
113
- // If no proxy was used and the request-uri has a DNS name, pin the credentialled
114
- // re-INVITE to the same server that challenged us by creating a proxy with its IP.
115
- // If a proxy was already set, leave it alone -- it's already the right destination.
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
- if (!options.proxy &&
118
- !originalUri.match(/sips?:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/)) {
119
- const transport = parseTransportToken(originalUri);
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});
@@ -301,7 +301,14 @@ class DrachtioAgent extends Emitter {
301
301
  transactionId: transactionId
302
302
  };
303
303
 
304
- const req = new Request(new SipMessage(msg), meta);
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
- const responseMsg = new SipMessage(msg);
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
- callback(null, new SipMessage(msg));
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
- const cdrSipMsg = new SipMessage(rawMsg);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drachtio-srf",
3
- "version": "5.0.20",
3
+ "version": "5.0.22",
4
4
  "description": "drachtio signaling resource framework",
5
5
  "main": "lib/srf.js",
6
6
  "types": "lib/@types/index.d.ts",
@@ -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
+ });