1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | Object.defineProperty(exports, "__esModule", { value: true });
|
18 | exports.setup = void 0;
|
19 | const resolver_1 = require("./resolver");
|
20 | const dns = require("dns");
|
21 | const util = require("util");
|
22 | const service_config_1 = require("./service-config");
|
23 | const constants_1 = require("./constants");
|
24 | const metadata_1 = require("./metadata");
|
25 | const logging = require("./logging");
|
26 | const constants_2 = require("./constants");
|
27 | const uri_parser_1 = require("./uri-parser");
|
28 | const net_1 = require("net");
|
29 | const backoff_timeout_1 = require("./backoff-timeout");
|
30 | const TRACER_NAME = 'dns_resolver';
|
31 | function trace(text) {
|
32 | logging.trace(constants_2.LogVerbosity.DEBUG, TRACER_NAME, text);
|
33 | }
|
34 |
|
35 |
|
36 |
|
37 | const DEFAULT_PORT = 443;
|
38 | const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30000;
|
39 | const resolveTxtPromise = util.promisify(dns.resolveTxt);
|
40 | const dnsLookupPromise = util.promisify(dns.lookup);
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | function mergeArrays(...arrays) {
|
46 | const result = [];
|
47 | for (let i = 0; i <
|
48 | Math.max.apply(null, arrays.map((array) => array.length)); i++) {
|
49 | for (const array of arrays) {
|
50 | if (i < array.length) {
|
51 | result.push(array[i]);
|
52 | }
|
53 | }
|
54 | }
|
55 | return result;
|
56 | }
|
57 |
|
58 |
|
59 |
|
60 | class DnsResolver {
|
61 | constructor(target, listener, channelOptions) {
|
62 | var _a, _b, _c;
|
63 | this.target = target;
|
64 | this.listener = listener;
|
65 | this.pendingLookupPromise = null;
|
66 | this.pendingTxtPromise = null;
|
67 | this.latestLookupResult = null;
|
68 | this.latestServiceConfig = null;
|
69 | this.latestServiceConfigError = null;
|
70 | this.continueResolving = false;
|
71 | this.isNextResolutionTimerRunning = false;
|
72 | this.isServiceConfigEnabled = true;
|
73 | trace('Resolver constructed for target ' + (0, uri_parser_1.uriToString)(target));
|
74 | const hostPort = (0, uri_parser_1.splitHostPort)(target.path);
|
75 | if (hostPort === null) {
|
76 | this.ipResult = null;
|
77 | this.dnsHostname = null;
|
78 | this.port = null;
|
79 | }
|
80 | else {
|
81 | if ((0, net_1.isIPv4)(hostPort.host) || (0, net_1.isIPv6)(hostPort.host)) {
|
82 | this.ipResult = [
|
83 | {
|
84 | host: hostPort.host,
|
85 | port: (_a = hostPort.port) !== null && _a !== void 0 ? _a : DEFAULT_PORT,
|
86 | },
|
87 | ];
|
88 | this.dnsHostname = null;
|
89 | this.port = null;
|
90 | }
|
91 | else {
|
92 | this.ipResult = null;
|
93 | this.dnsHostname = hostPort.host;
|
94 | this.port = (_b = hostPort.port) !== null && _b !== void 0 ? _b : DEFAULT_PORT;
|
95 | }
|
96 | }
|
97 | this.percentage = Math.random() * 100;
|
98 | if (channelOptions['grpc.service_config_disable_resolution'] === 1) {
|
99 | this.isServiceConfigEnabled = false;
|
100 | }
|
101 | this.defaultResolutionError = {
|
102 | code: constants_1.Status.UNAVAILABLE,
|
103 | details: `Name resolution failed for target ${(0, uri_parser_1.uriToString)(this.target)}`,
|
104 | metadata: new metadata_1.Metadata(),
|
105 | };
|
106 | const backoffOptions = {
|
107 | initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'],
|
108 | maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'],
|
109 | };
|
110 | this.backoff = new backoff_timeout_1.BackoffTimeout(() => {
|
111 | if (this.continueResolving) {
|
112 | this.startResolutionWithBackoff();
|
113 | }
|
114 | }, backoffOptions);
|
115 | this.backoff.unref();
|
116 | this.minTimeBetweenResolutionsMs = (_c = channelOptions['grpc.dns_min_time_between_resolutions_ms']) !== null && _c !== void 0 ? _c : DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS;
|
117 | this.nextResolutionTimer = setTimeout(() => { }, 0);
|
118 | clearTimeout(this.nextResolutionTimer);
|
119 | }
|
120 | |
121 |
|
122 |
|
123 |
|
124 | startResolution() {
|
125 | if (this.ipResult !== null) {
|
126 | trace('Returning IP address for target ' + (0, uri_parser_1.uriToString)(this.target));
|
127 | setImmediate(() => {
|
128 | this.listener.onSuccessfulResolution(this.ipResult, null, null, null, {});
|
129 | });
|
130 | this.backoff.stop();
|
131 | this.backoff.reset();
|
132 | return;
|
133 | }
|
134 | if (this.dnsHostname === null) {
|
135 | trace('Failed to parse DNS address ' + (0, uri_parser_1.uriToString)(this.target));
|
136 | setImmediate(() => {
|
137 | this.listener.onError({
|
138 | code: constants_1.Status.UNAVAILABLE,
|
139 | details: `Failed to parse DNS address ${(0, uri_parser_1.uriToString)(this.target)}`,
|
140 | metadata: new metadata_1.Metadata(),
|
141 | });
|
142 | });
|
143 | this.stopNextResolutionTimer();
|
144 | }
|
145 | else {
|
146 | if (this.pendingLookupPromise !== null) {
|
147 | return;
|
148 | }
|
149 | trace('Looking up DNS hostname ' + this.dnsHostname);
|
150 | |
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 | this.latestLookupResult = null;
|
157 | const hostname = this.dnsHostname;
|
158 | |
159 |
|
160 |
|
161 |
|
162 | this.pendingLookupPromise = dnsLookupPromise(hostname, { all: true });
|
163 | this.pendingLookupPromise.then((addressList) => {
|
164 | this.pendingLookupPromise = null;
|
165 | this.backoff.reset();
|
166 | this.backoff.stop();
|
167 | const ip4Addresses = addressList.filter((addr) => addr.family === 4);
|
168 | const ip6Addresses = addressList.filter((addr) => addr.family === 6);
|
169 | this.latestLookupResult = mergeArrays(ip6Addresses, ip4Addresses).map((addr) => ({ host: addr.address, port: +this.port }));
|
170 | const allAddressesString = '[' +
|
171 | this.latestLookupResult
|
172 | .map((addr) => addr.host + ':' + addr.port)
|
173 | .join(',') +
|
174 | ']';
|
175 | trace('Resolved addresses for target ' +
|
176 | (0, uri_parser_1.uriToString)(this.target) +
|
177 | ': ' +
|
178 | allAddressesString);
|
179 | if (this.latestLookupResult.length === 0) {
|
180 | this.listener.onError(this.defaultResolutionError);
|
181 | return;
|
182 | }
|
183 | |
184 |
|
185 |
|
186 |
|
187 | this.listener.onSuccessfulResolution(this.latestLookupResult, this.latestServiceConfig, this.latestServiceConfigError, null, {});
|
188 | }, (err) => {
|
189 | trace('Resolution error for target ' +
|
190 | (0, uri_parser_1.uriToString)(this.target) +
|
191 | ': ' +
|
192 | err.message);
|
193 | this.pendingLookupPromise = null;
|
194 | this.stopNextResolutionTimer();
|
195 | this.listener.onError(this.defaultResolutionError);
|
196 | });
|
197 | |
198 |
|
199 | if (this.isServiceConfigEnabled && this.pendingTxtPromise === null) {
|
200 | |
201 |
|
202 |
|
203 | this.pendingTxtPromise = resolveTxtPromise(hostname);
|
204 | this.pendingTxtPromise.then((txtRecord) => {
|
205 | this.pendingTxtPromise = null;
|
206 | try {
|
207 | this.latestServiceConfig = (0, service_config_1.extractAndSelectServiceConfig)(txtRecord, this.percentage);
|
208 | }
|
209 | catch (err) {
|
210 | this.latestServiceConfigError = {
|
211 | code: constants_1.Status.UNAVAILABLE,
|
212 | details: 'Parsing service config failed',
|
213 | metadata: new metadata_1.Metadata(),
|
214 | };
|
215 | }
|
216 | if (this.latestLookupResult !== null) {
|
217 | |
218 |
|
219 |
|
220 |
|
221 | this.listener.onSuccessfulResolution(this.latestLookupResult, this.latestServiceConfig, this.latestServiceConfigError, null, {});
|
222 | }
|
223 | }, (err) => {
|
224 | |
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 | });
|
232 | }
|
233 | }
|
234 | }
|
235 | startNextResolutionTimer() {
|
236 | var _a, _b;
|
237 | clearTimeout(this.nextResolutionTimer);
|
238 | this.nextResolutionTimer = (_b = (_a = setTimeout(() => {
|
239 | this.stopNextResolutionTimer();
|
240 | if (this.continueResolving) {
|
241 | this.startResolutionWithBackoff();
|
242 | }
|
243 | }, this.minTimeBetweenResolutionsMs)).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
244 | this.isNextResolutionTimerRunning = true;
|
245 | }
|
246 | stopNextResolutionTimer() {
|
247 | clearTimeout(this.nextResolutionTimer);
|
248 | this.isNextResolutionTimerRunning = false;
|
249 | }
|
250 | startResolutionWithBackoff() {
|
251 | if (this.pendingLookupPromise === null) {
|
252 | this.continueResolving = false;
|
253 | this.startResolution();
|
254 | this.backoff.runOnce();
|
255 | this.startNextResolutionTimer();
|
256 | }
|
257 | }
|
258 | updateResolution() {
|
259 | |
260 |
|
261 |
|
262 |
|
263 | if (this.pendingLookupPromise === null) {
|
264 | if (this.isNextResolutionTimerRunning || this.backoff.isRunning()) {
|
265 | this.continueResolving = true;
|
266 | }
|
267 | else {
|
268 | this.startResolutionWithBackoff();
|
269 | }
|
270 | }
|
271 | }
|
272 | destroy() {
|
273 | this.continueResolving = false;
|
274 | this.backoff.stop();
|
275 | this.stopNextResolutionTimer();
|
276 | }
|
277 | |
278 |
|
279 |
|
280 |
|
281 |
|
282 | static getDefaultAuthority(target) {
|
283 | return target.path;
|
284 | }
|
285 | }
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | function setup() {
|
291 | (0, resolver_1.registerResolver)('dns', DnsResolver);
|
292 | (0, resolver_1.registerDefaultScheme)('dns');
|
293 | }
|
294 | exports.setup = setup;
|
295 |
|
\ | No newline at end of file |