333
schangxiang@126.com
2025-09-19 18966e02fb573c7e2bb0c6426ed792b38b910940
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
'use strict';
 
const Logger = require('../connection/logger');
const EventEmitter = require('events').EventEmitter;
const dns = require('dns');
/**
 * Determines whether a provided address matches the provided parent domain in order
 * to avoid certain attack vectors.
 *
 * @param {String} srvAddress The address to check against a domain
 * @param {String} parentDomain The domain to check the provided address against
 * @return {Boolean} Whether the provided address matches the parent domain
 */
function matchesParentDomain(srvAddress, parentDomain) {
  const regex = /^.*?\./;
  const srv = `.${srvAddress.replace(regex, '')}`;
  const parent = `.${parentDomain.replace(regex, '')}`;
  return srv.endsWith(parent);
}
 
class SrvPollingEvent {
  constructor(srvRecords) {
    this.srvRecords = srvRecords;
  }
 
  addresses() {
    return new Set(this.srvRecords.map(record => `${record.name}:${record.port}`));
  }
}
 
class SrvPoller extends EventEmitter {
  /**
   * @param {object} options
   * @param {string} options.srvHost
   * @param {number} [options.heartbeatFrequencyMS]
   * @param {function} [options.logger]
   * @param {string} [options.loggerLevel]
   */
  constructor(options) {
    super();
 
    if (!options || !options.srvHost) {
      throw new TypeError('options for SrvPoller must exist and include srvHost');
    }
 
    this.srvHost = options.srvHost;
    this.rescanSrvIntervalMS = 60000;
    this.heartbeatFrequencyMS = options.heartbeatFrequencyMS || 10000;
    this.logger = Logger('srvPoller', options);
 
    this.haMode = false;
    this.generation = 0;
 
    this._timeout = null;
  }
 
  get srvAddress() {
    return `_mongodb._tcp.${this.srvHost}`;
  }
 
  get intervalMS() {
    return this.haMode ? this.heartbeatFrequencyMS : this.rescanSrvIntervalMs;
  }
 
  start() {
    if (!this._timeout) {
      this.schedule();
    }
  }
 
  stop() {
    if (this._timeout) {
      clearTimeout(this._timeout);
      this.generation += 1;
      this._timeout = null;
    }
  }
 
  schedule() {
    clearTimeout(this._timeout);
    this._timeout = setTimeout(() => this._poll(), this.intervalMS);
  }
 
  success(srvRecords) {
    this.haMode = false;
    this.schedule();
    this.emit('srvRecordDiscovery', new SrvPollingEvent(srvRecords));
  }
 
  failure(message, obj) {
    this.logger.warn(message, obj);
    this.haMode = true;
    this.schedule();
  }
 
  parentDomainMismatch(srvRecord) {
    this.logger.warn(
      `parent domain mismatch on SRV record (${srvRecord.name}:${srvRecord.port})`,
      srvRecord
    );
  }
 
  _poll() {
    const generation = this.generation;
    dns.resolveSrv(this.srvAddress, (err, srvRecords) => {
      if (generation !== this.generation) {
        return;
      }
 
      if (err) {
        this.failure('DNS error', err);
        return;
      }
 
      const finalAddresses = [];
      srvRecords.forEach(record => {
        if (matchesParentDomain(record.name, this.srvHost)) {
          finalAddresses.push(record);
        } else {
          this.parentDomainMismatch(record);
        }
      });
 
      if (!finalAddresses.length) {
        this.failure('No valid addresses found at host');
        return;
      }
 
      this.success(finalAddresses);
    });
  }
}
 
module.exports.SrvPollingEvent = SrvPollingEvent;
module.exports.SrvPoller = SrvPoller;