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 | trace('Resolver constructed for target ' + uri_parser_1.uriToString(target));
|
73 | const hostPort = uri_parser_1.splitHostPort(target.path);
|
74 | if (hostPort === null) {
|
75 | this.ipResult = null;
|
76 | this.dnsHostname = null;
|
77 | this.port = null;
|
78 | }
|
79 | else {
|
80 | if (net_1.isIPv4(hostPort.host) || net_1.isIPv6(hostPort.host)) {
|
81 | this.ipResult = [
|
82 | {
|
83 | host: hostPort.host,
|
84 | port: (_a = hostPort.port) !== null && _a !== void 0 ? _a : DEFAULT_PORT,
|
85 | },
|
86 | ];
|
87 | this.dnsHostname = null;
|
88 | this.port = null;
|
89 | }
|
90 | else {
|
91 | this.ipResult = null;
|
92 | this.dnsHostname = hostPort.host;
|
93 | this.port = (_b = hostPort.port) !== null && _b !== void 0 ? _b : DEFAULT_PORT;
|
94 | }
|
95 | }
|
96 | this.percentage = Math.random() * 100;
|
97 | this.defaultResolutionError = {
|
98 | code: constants_1.Status.UNAVAILABLE,
|
99 | details: `Name resolution failed for target ${uri_parser_1.uriToString(this.target)}`,
|
100 | metadata: new metadata_1.Metadata(),
|
101 | };
|
102 | const backoffOptions = {
|
103 | initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'],
|
104 | maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'],
|
105 | };
|
106 | this.backoff = new backoff_timeout_1.BackoffTimeout(() => {
|
107 | if (this.continueResolving) {
|
108 | this.startResolutionWithBackoff();
|
109 | }
|
110 | }, backoffOptions);
|
111 | this.backoff.unref();
|
112 | this.minTimeBetweenResolutionsMs = (_c = channelOptions['grpc.dns_min_time_between_resolutions_ms']) !== null && _c !== void 0 ? _c : DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS;
|
113 | this.nextResolutionTimer = setTimeout(() => { }, 0);
|
114 | clearTimeout(this.nextResolutionTimer);
|
115 | }
|
116 | |
117 |
|
118 |
|
119 |
|
120 | startResolution() {
|
121 | if (this.ipResult !== null) {
|
122 | trace('Returning IP address for target ' + uri_parser_1.uriToString(this.target));
|
123 | setImmediate(() => {
|
124 | this.listener.onSuccessfulResolution(this.ipResult, null, null, null, {});
|
125 | });
|
126 | this.backoff.stop();
|
127 | this.backoff.reset();
|
128 | return;
|
129 | }
|
130 | if (this.dnsHostname === null) {
|
131 | trace('Failed to parse DNS address ' + uri_parser_1.uriToString(this.target));
|
132 | setImmediate(() => {
|
133 | this.listener.onError({
|
134 | code: constants_1.Status.UNAVAILABLE,
|
135 | details: `Failed to parse DNS address ${uri_parser_1.uriToString(this.target)}`,
|
136 | metadata: new metadata_1.Metadata(),
|
137 | });
|
138 | });
|
139 | this.stopNextResolutionTimer();
|
140 | }
|
141 | else {
|
142 | if (this.pendingLookupPromise !== null) {
|
143 | return;
|
144 | }
|
145 | trace('Looking up DNS hostname ' + this.dnsHostname);
|
146 | |
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 | this.latestLookupResult = null;
|
153 | const hostname = this.dnsHostname;
|
154 | |
155 |
|
156 |
|
157 |
|
158 | this.pendingLookupPromise = dnsLookupPromise(hostname, { all: true });
|
159 | this.pendingLookupPromise.then((addressList) => {
|
160 | this.pendingLookupPromise = null;
|
161 | this.backoff.reset();
|
162 | this.backoff.stop();
|
163 | const ip4Addresses = addressList.filter((addr) => addr.family === 4);
|
164 | const ip6Addresses = addressList.filter((addr) => addr.family === 6);
|
165 | this.latestLookupResult = mergeArrays(ip6Addresses, ip4Addresses).map((addr) => ({ host: addr.address, port: +this.port }));
|
166 | const allAddressesString = '[' +
|
167 | this.latestLookupResult
|
168 | .map((addr) => addr.host + ':' + addr.port)
|
169 | .join(',') +
|
170 | ']';
|
171 | trace('Resolved addresses for target ' +
|
172 | uri_parser_1.uriToString(this.target) +
|
173 | ': ' +
|
174 | allAddressesString);
|
175 | if (this.latestLookupResult.length === 0) {
|
176 | this.listener.onError(this.defaultResolutionError);
|
177 | return;
|
178 | }
|
179 | |
180 |
|
181 |
|
182 |
|
183 | this.listener.onSuccessfulResolution(this.latestLookupResult, this.latestServiceConfig, this.latestServiceConfigError, null, {});
|
184 | }, (err) => {
|
185 | trace('Resolution error for target ' +
|
186 | uri_parser_1.uriToString(this.target) +
|
187 | ': ' +
|
188 | err.message);
|
189 | this.pendingLookupPromise = null;
|
190 | this.stopNextResolutionTimer();
|
191 | this.listener.onError(this.defaultResolutionError);
|
192 | });
|
193 | |
194 |
|
195 | if (this.pendingTxtPromise === null) {
|
196 | |
197 |
|
198 |
|
199 | this.pendingTxtPromise = resolveTxtPromise(hostname);
|
200 | this.pendingTxtPromise.then((txtRecord) => {
|
201 | this.pendingTxtPromise = null;
|
202 | try {
|
203 | this.latestServiceConfig = service_config_1.extractAndSelectServiceConfig(txtRecord, this.percentage);
|
204 | }
|
205 | catch (err) {
|
206 | this.latestServiceConfigError = {
|
207 | code: constants_1.Status.UNAVAILABLE,
|
208 | details: 'Parsing service config failed',
|
209 | metadata: new metadata_1.Metadata(),
|
210 | };
|
211 | }
|
212 | if (this.latestLookupResult !== null) {
|
213 | |
214 |
|
215 |
|
216 |
|
217 | this.listener.onSuccessfulResolution(this.latestLookupResult, this.latestServiceConfig, this.latestServiceConfigError, null, {});
|
218 | }
|
219 | }, (err) => {
|
220 | |
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | });
|
228 | }
|
229 | }
|
230 | }
|
231 | startNextResolutionTimer() {
|
232 | var _a, _b;
|
233 | clearTimeout(this.nextResolutionTimer);
|
234 | this.nextResolutionTimer = (_b = (_a = setTimeout(() => {
|
235 | this.stopNextResolutionTimer();
|
236 | if (this.continueResolving) {
|
237 | this.startResolutionWithBackoff();
|
238 | }
|
239 | }, this.minTimeBetweenResolutionsMs)).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
240 | this.isNextResolutionTimerRunning = true;
|
241 | }
|
242 | stopNextResolutionTimer() {
|
243 | clearTimeout(this.nextResolutionTimer);
|
244 | this.isNextResolutionTimerRunning = false;
|
245 | }
|
246 | startResolutionWithBackoff() {
|
247 | if (this.pendingLookupPromise === null) {
|
248 | this.continueResolving = false;
|
249 | this.startResolution();
|
250 | this.backoff.runOnce();
|
251 | this.startNextResolutionTimer();
|
252 | }
|
253 | }
|
254 | updateResolution() {
|
255 | |
256 |
|
257 |
|
258 |
|
259 | if (this.pendingLookupPromise === null) {
|
260 | if (this.isNextResolutionTimerRunning || this.backoff.isRunning()) {
|
261 | this.continueResolving = true;
|
262 | }
|
263 | else {
|
264 | this.startResolutionWithBackoff();
|
265 | }
|
266 | }
|
267 | }
|
268 | destroy() {
|
269 | this.continueResolving = false;
|
270 | this.backoff.stop();
|
271 | this.stopNextResolutionTimer();
|
272 | }
|
273 | |
274 |
|
275 |
|
276 |
|
277 |
|
278 | static getDefaultAuthority(target) {
|
279 | return target.path;
|
280 | }
|
281 | }
|
282 |
|
283 |
|
284 |
|
285 |
|
286 | function setup() {
|
287 | resolver_1.registerResolver('dns', DnsResolver);
|
288 | resolver_1.registerDefaultScheme('dns');
|
289 | }
|
290 | exports.setup = setup;
|
291 |
|
\ | No newline at end of file |