@aws-sdk/util-dns 3.271.0 → 3.274.0

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.
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NodeDnsLookupHostResolver = void 0;
4
4
  const types_1 = require("@aws-sdk/types");
5
5
  const dns_1 = require("dns");
6
+ const HostEntryTable_1 = require("./util/HostEntryTable");
6
7
  const NODE_DNS_FAMILY_TO_HOST_ADDRESS_TYPE = {
7
8
  4: types_1.HostAddressType.A,
8
9
  6: types_1.HostAddressType.AAAA,
@@ -13,9 +14,59 @@ const DNS_LOOKUP_OPTIONS = {
13
14
  verbatim: true,
14
15
  };
15
16
  class NodeDnsLookupHostResolver {
17
+ constructor({ ttlMs = NodeDnsLookupHostResolver.DEFAULT_TTL_MS, cache = NodeDnsLookupHostResolver.createDefaultCacheProvider(), nodeDnsLookup = NodeDnsLookupHostResolver.DEFAULT_NODE_DNS_LOOKUP, } = {}) {
18
+ this.ttlMs = ttlMs;
19
+ this.cache = cache;
20
+ this.nodeDnsLookup = nodeDnsLookup;
21
+ }
16
22
  async resolveAddress(args) {
23
+ const possibleHostEntry = this.cache.get(args.hostName);
24
+ const newNextTimestampToUpdateMs = Date.now() + this.ttlMs;
25
+ if (possibleHostEntry === undefined) {
26
+ const addresses = await this.nodeDnsLookupResolveAddress(args);
27
+ this.cache.set(args, addresses, newNextTimestampToUpdateMs);
28
+ }
29
+ const hostEntry = this.cache.get(args.hostName);
30
+ if (possibleHostEntry !== undefined && Date.now() >= hostEntry.nextTimestampToUpdateMs) {
31
+ try {
32
+ const addresses = await this.nodeDnsLookupResolveAddress(args);
33
+ hostEntry.updateRecords(addresses, newNextTimestampToUpdateMs);
34
+ }
35
+ catch (error) {
36
+ console.error(`Could not update DNS address cache for "${args.hostName}": ${error}`);
37
+ }
38
+ }
39
+ hostEntry.processRecords();
40
+ const result = [];
41
+ if (hostEntry.aRecords.length > 0) {
42
+ result.push(hostEntry.aRecords.cycle());
43
+ }
44
+ if (hostEntry.aaaaRecords.length > 0) {
45
+ result.push(hostEntry.aaaaRecords.cycle());
46
+ }
47
+ if (result.length === 0) {
48
+ throw new Error(`Could not resolve addresses for "${args.hostName}"`);
49
+ }
50
+ return result;
51
+ }
52
+ reportFailureOnAddress(addr) {
53
+ const hostEntry = this.cache.get(addr.hostName);
54
+ if (hostEntry === undefined) {
55
+ throw new Error(`Could not find cached host name "${addr.hostName}"`);
56
+ }
57
+ hostEntry.failAddressInRecords(addr);
58
+ }
59
+ purgeCache(args) {
60
+ if (args === null || args === void 0 ? void 0 : args.hostName) {
61
+ this.cache.delete(args.hostName);
62
+ }
63
+ else {
64
+ this.cache.clear();
65
+ }
66
+ }
67
+ async nodeDnsLookupResolveAddress(args) {
17
68
  const addresses = [];
18
- const ipEntries = await dns_1.promises.lookup(args.hostName, DNS_LOOKUP_OPTIONS);
69
+ const ipEntries = await this.nodeDnsLookup(args.hostName, DNS_LOOKUP_OPTIONS);
19
70
  for (const { address, family } of ipEntries) {
20
71
  const addressType = NODE_DNS_FAMILY_TO_HOST_ADDRESS_TYPE[family];
21
72
  if (addressType === undefined) {
@@ -30,11 +81,8 @@ class NodeDnsLookupHostResolver {
30
81
  }
31
82
  return addresses;
32
83
  }
33
- reportFailureOnAddress(addr) {
34
- throw new Error("reportFailureOnAddress(addr) is not implemented");
35
- }
36
- purgeCache(args) {
37
- throw new Error("purgeCache(args?) is not implemented");
38
- }
39
84
  }
40
85
  exports.NodeDnsLookupHostResolver = NodeDnsLookupHostResolver;
86
+ NodeDnsLookupHostResolver.DEFAULT_TTL_MS = 30000;
87
+ NodeDnsLookupHostResolver.createDefaultCacheProvider = () => new HostEntryTable_1.HostEntryTable();
88
+ NodeDnsLookupHostResolver.DEFAULT_NODE_DNS_LOOKUP = dns_1.promises.lookup;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HostAddressEntryCollection = void 0;
4
+ class HostAddressEntryCollection {
5
+ constructor() {
6
+ this.data = [];
7
+ }
8
+ get length() {
9
+ return this.data.length;
10
+ }
11
+ cycle(collection) {
12
+ if (this.data.length === 0) {
13
+ throw new Error("Cannot cycle an empty collection");
14
+ }
15
+ const entry = this.data.shift();
16
+ (collection || this).append(entry);
17
+ return entry;
18
+ }
19
+ append(entry) {
20
+ this.data.push(entry);
21
+ }
22
+ remove(entry) {
23
+ if (this.length === 0) {
24
+ throw new Error("Cannot remove from an empty collection");
25
+ }
26
+ const index = this.data.findIndex((e) => e.address === entry.address);
27
+ const removedEntry = this.data[index];
28
+ this.data.splice(index, 1);
29
+ return removedEntry;
30
+ }
31
+ [Symbol.iterator]() {
32
+ return this.data[Symbol.iterator]();
33
+ }
34
+ }
35
+ exports.HostAddressEntryCollection = HostAddressEntryCollection;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HostEntry = void 0;
4
+ const types_1 = require("@aws-sdk/types");
5
+ const HostAddressEntryCollection_1 = require("./HostAddressEntryCollection");
6
+ class HostEntry {
7
+ constructor(nextTimestampToProcessMs) {
8
+ this.aaaaRecords = new HostAddressEntryCollection_1.HostAddressEntryCollection();
9
+ this.aRecords = new HostAddressEntryCollection_1.HostAddressEntryCollection();
10
+ this.failedAaaaRecords = new HostAddressEntryCollection_1.HostAddressEntryCollection();
11
+ this.failedARecords = new HostAddressEntryCollection_1.HostAddressEntryCollection();
12
+ this.nextTimestampToUpdateMs = nextTimestampToProcessMs;
13
+ }
14
+ updateRecords(addresses, expirationTtlMs) {
15
+ this.nextTimestampToUpdateMs = expirationTtlMs;
16
+ const addressesToAppend = [];
17
+ for (const freshAddress of addresses) {
18
+ const hostAddressEntry = this.findAddress(freshAddress);
19
+ if (hostAddressEntry !== undefined) {
20
+ hostAddressEntry.expirationTtlMs = expirationTtlMs;
21
+ continue;
22
+ }
23
+ addressesToAppend.push(freshAddress);
24
+ }
25
+ for (const addressToAppend of addressesToAppend) {
26
+ const hostAddressEntry = this.findAddress(addressToAppend);
27
+ if (hostAddressEntry !== undefined) {
28
+ continue;
29
+ }
30
+ const successRecords = this.getGoodRecords(addressToAppend);
31
+ successRecords.append(Object.assign(addressToAppend, {
32
+ expirationTtlMs,
33
+ }));
34
+ }
35
+ }
36
+ processRecords() {
37
+ this.processRecordsAddressType(this.aRecords, this.failedARecords);
38
+ this.processRecordsAddressType(this.aaaaRecords, this.failedAaaaRecords);
39
+ }
40
+ failAddressInRecords(address) {
41
+ const successRecords = this.getGoodRecords(address);
42
+ const failedRecords = this.getFailedRecords(address);
43
+ const recordsToRemove = [];
44
+ for (const hostAddressEntry of successRecords) {
45
+ if (hostAddressEntry.address === address.address) {
46
+ recordsToRemove.push(hostAddressEntry);
47
+ failedRecords.append(hostAddressEntry);
48
+ }
49
+ }
50
+ for (const recordToRemove of recordsToRemove) {
51
+ successRecords.remove(recordToRemove);
52
+ }
53
+ }
54
+ findAddress(address) {
55
+ const successRecords = this.getGoodRecords(address);
56
+ for (const hostAddressEntry of successRecords) {
57
+ if (address.address === hostAddressEntry.address) {
58
+ return hostAddressEntry;
59
+ }
60
+ }
61
+ const failedRecords = this.getFailedRecords(address);
62
+ for (const hostAddressEntry of failedRecords) {
63
+ if (address.address === hostAddressEntry.address) {
64
+ return hostAddressEntry;
65
+ }
66
+ }
67
+ return undefined;
68
+ }
69
+ processRecordsAddressType(successRecords, failedRecords) {
70
+ const successRecordsToRemove = [];
71
+ let successIndex = 0;
72
+ for (const hostAddressEntry of successRecords) {
73
+ if (successIndex === successRecords.length - 1) {
74
+ break;
75
+ }
76
+ if (Date.now() >= hostAddressEntry.expirationTtlMs) {
77
+ successRecordsToRemove.push(hostAddressEntry);
78
+ }
79
+ successIndex++;
80
+ }
81
+ for (const recordToRemove of successRecordsToRemove) {
82
+ successRecords.remove(recordToRemove);
83
+ }
84
+ const failedRecordsToRemove = [];
85
+ let failedIndex = 0;
86
+ for (const hostAddressEntry of failedRecords) {
87
+ if (failedIndex === failedRecords.length - 1) {
88
+ break;
89
+ }
90
+ if (Date.now() >= hostAddressEntry.expirationTtlMs) {
91
+ failedRecordsToRemove.push(hostAddressEntry);
92
+ }
93
+ failedIndex++;
94
+ }
95
+ for (const recordToRemove of failedRecordsToRemove) {
96
+ failedRecords.remove(recordToRemove);
97
+ }
98
+ if (successRecords.length === 0) {
99
+ let hostAddressEntryToPromote = undefined;
100
+ for (const hostAddressEntry of failedRecords) {
101
+ if (Date.now() >= hostAddressEntry.expirationTtlMs) {
102
+ continue;
103
+ }
104
+ hostAddressEntryToPromote = hostAddressEntry;
105
+ break;
106
+ }
107
+ if (hostAddressEntryToPromote !== undefined) {
108
+ failedRecords.remove(hostAddressEntryToPromote);
109
+ successRecords.append(hostAddressEntryToPromote);
110
+ }
111
+ }
112
+ }
113
+ getGoodRecords(address) {
114
+ return address.addressType === types_1.HostAddressType.AAAA ? this.aaaaRecords : this.aRecords;
115
+ }
116
+ getFailedRecords(address) {
117
+ return address.addressType === types_1.HostAddressType.AAAA ? this.failedAaaaRecords : this.failedARecords;
118
+ }
119
+ }
120
+ exports.HostEntry = HostEntry;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HostEntryTable = void 0;
4
+ const HostEntry_1 = require("./HostEntry");
5
+ class HostEntryTable {
6
+ constructor() {
7
+ this.map = new Map();
8
+ }
9
+ set(args, addresses, nextTimestampToProcessMs) {
10
+ const hostEntry = new HostEntry_1.HostEntry(nextTimestampToProcessMs);
11
+ hostEntry.updateRecords(addresses, nextTimestampToProcessMs);
12
+ this.map.set(args.hostName, hostEntry);
13
+ }
14
+ get(hostName) {
15
+ return this.map.get(hostName);
16
+ }
17
+ delete(hostName) {
18
+ this.map.delete(hostName);
19
+ }
20
+ clear() {
21
+ this.map.clear();
22
+ }
23
+ get size() {
24
+ return this.map.size;
25
+ }
26
+ }
27
+ exports.HostEntryTable = HostEntryTable;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { HostAddressType } from "@aws-sdk/types";
2
2
  import { ALL, promises as dnsPromises, V4MAPPED } from "dns";
3
+ import { HostEntryTable } from "./util/HostEntryTable";
3
4
  const NODE_DNS_FAMILY_TO_HOST_ADDRESS_TYPE = {
4
5
  4: HostAddressType.A,
5
6
  6: HostAddressType.AAAA,
@@ -10,9 +11,59 @@ const DNS_LOOKUP_OPTIONS = {
10
11
  verbatim: true,
11
12
  };
12
13
  export class NodeDnsLookupHostResolver {
14
+ constructor({ ttlMs = NodeDnsLookupHostResolver.DEFAULT_TTL_MS, cache = NodeDnsLookupHostResolver.createDefaultCacheProvider(), nodeDnsLookup = NodeDnsLookupHostResolver.DEFAULT_NODE_DNS_LOOKUP, } = {}) {
15
+ this.ttlMs = ttlMs;
16
+ this.cache = cache;
17
+ this.nodeDnsLookup = nodeDnsLookup;
18
+ }
13
19
  async resolveAddress(args) {
20
+ const possibleHostEntry = this.cache.get(args.hostName);
21
+ const newNextTimestampToUpdateMs = Date.now() + this.ttlMs;
22
+ if (possibleHostEntry === undefined) {
23
+ const addresses = await this.nodeDnsLookupResolveAddress(args);
24
+ this.cache.set(args, addresses, newNextTimestampToUpdateMs);
25
+ }
26
+ const hostEntry = this.cache.get(args.hostName);
27
+ if (possibleHostEntry !== undefined && Date.now() >= hostEntry.nextTimestampToUpdateMs) {
28
+ try {
29
+ const addresses = await this.nodeDnsLookupResolveAddress(args);
30
+ hostEntry.updateRecords(addresses, newNextTimestampToUpdateMs);
31
+ }
32
+ catch (error) {
33
+ console.error(`Could not update DNS address cache for "${args.hostName}": ${error}`);
34
+ }
35
+ }
36
+ hostEntry.processRecords();
37
+ const result = [];
38
+ if (hostEntry.aRecords.length > 0) {
39
+ result.push(hostEntry.aRecords.cycle());
40
+ }
41
+ if (hostEntry.aaaaRecords.length > 0) {
42
+ result.push(hostEntry.aaaaRecords.cycle());
43
+ }
44
+ if (result.length === 0) {
45
+ throw new Error(`Could not resolve addresses for "${args.hostName}"`);
46
+ }
47
+ return result;
48
+ }
49
+ reportFailureOnAddress(addr) {
50
+ const hostEntry = this.cache.get(addr.hostName);
51
+ if (hostEntry === undefined) {
52
+ throw new Error(`Could not find cached host name "${addr.hostName}"`);
53
+ }
54
+ hostEntry.failAddressInRecords(addr);
55
+ }
56
+ purgeCache(args) {
57
+ if (args?.hostName) {
58
+ this.cache.delete(args.hostName);
59
+ }
60
+ else {
61
+ this.cache.clear();
62
+ }
63
+ }
64
+ async nodeDnsLookupResolveAddress(args) {
14
65
  const addresses = [];
15
- const ipEntries = await dnsPromises.lookup(args.hostName, DNS_LOOKUP_OPTIONS);
66
+ const ipEntries = await this.nodeDnsLookup(args.hostName, DNS_LOOKUP_OPTIONS);
16
67
  for (const { address, family } of ipEntries) {
17
68
  const addressType = NODE_DNS_FAMILY_TO_HOST_ADDRESS_TYPE[family];
18
69
  if (addressType === undefined) {
@@ -27,10 +78,7 @@ export class NodeDnsLookupHostResolver {
27
78
  }
28
79
  return addresses;
29
80
  }
30
- reportFailureOnAddress(addr) {
31
- throw new Error("reportFailureOnAddress(addr) is not implemented");
32
- }
33
- purgeCache(args) {
34
- throw new Error("purgeCache(args?) is not implemented");
35
- }
36
81
  }
82
+ NodeDnsLookupHostResolver.DEFAULT_TTL_MS = 30000;
83
+ NodeDnsLookupHostResolver.createDefaultCacheProvider = () => new HostEntryTable();
84
+ NodeDnsLookupHostResolver.DEFAULT_NODE_DNS_LOOKUP = dnsPromises.lookup;
@@ -0,0 +1,31 @@
1
+ export class HostAddressEntryCollection {
2
+ constructor() {
3
+ this.data = [];
4
+ }
5
+ get length() {
6
+ return this.data.length;
7
+ }
8
+ cycle(collection) {
9
+ if (this.data.length === 0) {
10
+ throw new Error("Cannot cycle an empty collection");
11
+ }
12
+ const entry = this.data.shift();
13
+ (collection || this).append(entry);
14
+ return entry;
15
+ }
16
+ append(entry) {
17
+ this.data.push(entry);
18
+ }
19
+ remove(entry) {
20
+ if (this.length === 0) {
21
+ throw new Error("Cannot remove from an empty collection");
22
+ }
23
+ const index = this.data.findIndex((e) => e.address === entry.address);
24
+ const removedEntry = this.data[index];
25
+ this.data.splice(index, 1);
26
+ return removedEntry;
27
+ }
28
+ [Symbol.iterator]() {
29
+ return this.data[Symbol.iterator]();
30
+ }
31
+ }
@@ -0,0 +1,116 @@
1
+ import { HostAddressType } from "@aws-sdk/types";
2
+ import { HostAddressEntryCollection } from "./HostAddressEntryCollection";
3
+ export class HostEntry {
4
+ constructor(nextTimestampToProcessMs) {
5
+ this.aaaaRecords = new HostAddressEntryCollection();
6
+ this.aRecords = new HostAddressEntryCollection();
7
+ this.failedAaaaRecords = new HostAddressEntryCollection();
8
+ this.failedARecords = new HostAddressEntryCollection();
9
+ this.nextTimestampToUpdateMs = nextTimestampToProcessMs;
10
+ }
11
+ updateRecords(addresses, expirationTtlMs) {
12
+ this.nextTimestampToUpdateMs = expirationTtlMs;
13
+ const addressesToAppend = [];
14
+ for (const freshAddress of addresses) {
15
+ const hostAddressEntry = this.findAddress(freshAddress);
16
+ if (hostAddressEntry !== undefined) {
17
+ hostAddressEntry.expirationTtlMs = expirationTtlMs;
18
+ continue;
19
+ }
20
+ addressesToAppend.push(freshAddress);
21
+ }
22
+ for (const addressToAppend of addressesToAppend) {
23
+ const hostAddressEntry = this.findAddress(addressToAppend);
24
+ if (hostAddressEntry !== undefined) {
25
+ continue;
26
+ }
27
+ const successRecords = this.getGoodRecords(addressToAppend);
28
+ successRecords.append(Object.assign(addressToAppend, {
29
+ expirationTtlMs,
30
+ }));
31
+ }
32
+ }
33
+ processRecords() {
34
+ this.processRecordsAddressType(this.aRecords, this.failedARecords);
35
+ this.processRecordsAddressType(this.aaaaRecords, this.failedAaaaRecords);
36
+ }
37
+ failAddressInRecords(address) {
38
+ const successRecords = this.getGoodRecords(address);
39
+ const failedRecords = this.getFailedRecords(address);
40
+ const recordsToRemove = [];
41
+ for (const hostAddressEntry of successRecords) {
42
+ if (hostAddressEntry.address === address.address) {
43
+ recordsToRemove.push(hostAddressEntry);
44
+ failedRecords.append(hostAddressEntry);
45
+ }
46
+ }
47
+ for (const recordToRemove of recordsToRemove) {
48
+ successRecords.remove(recordToRemove);
49
+ }
50
+ }
51
+ findAddress(address) {
52
+ const successRecords = this.getGoodRecords(address);
53
+ for (const hostAddressEntry of successRecords) {
54
+ if (address.address === hostAddressEntry.address) {
55
+ return hostAddressEntry;
56
+ }
57
+ }
58
+ const failedRecords = this.getFailedRecords(address);
59
+ for (const hostAddressEntry of failedRecords) {
60
+ if (address.address === hostAddressEntry.address) {
61
+ return hostAddressEntry;
62
+ }
63
+ }
64
+ return undefined;
65
+ }
66
+ processRecordsAddressType(successRecords, failedRecords) {
67
+ const successRecordsToRemove = [];
68
+ let successIndex = 0;
69
+ for (const hostAddressEntry of successRecords) {
70
+ if (successIndex === successRecords.length - 1) {
71
+ break;
72
+ }
73
+ if (Date.now() >= hostAddressEntry.expirationTtlMs) {
74
+ successRecordsToRemove.push(hostAddressEntry);
75
+ }
76
+ successIndex++;
77
+ }
78
+ for (const recordToRemove of successRecordsToRemove) {
79
+ successRecords.remove(recordToRemove);
80
+ }
81
+ const failedRecordsToRemove = [];
82
+ let failedIndex = 0;
83
+ for (const hostAddressEntry of failedRecords) {
84
+ if (failedIndex === failedRecords.length - 1) {
85
+ break;
86
+ }
87
+ if (Date.now() >= hostAddressEntry.expirationTtlMs) {
88
+ failedRecordsToRemove.push(hostAddressEntry);
89
+ }
90
+ failedIndex++;
91
+ }
92
+ for (const recordToRemove of failedRecordsToRemove) {
93
+ failedRecords.remove(recordToRemove);
94
+ }
95
+ if (successRecords.length === 0) {
96
+ let hostAddressEntryToPromote = undefined;
97
+ for (const hostAddressEntry of failedRecords) {
98
+ if (Date.now() >= hostAddressEntry.expirationTtlMs) {
99
+ continue;
100
+ }
101
+ hostAddressEntryToPromote = hostAddressEntry;
102
+ break;
103
+ }
104
+ if (hostAddressEntryToPromote !== undefined) {
105
+ failedRecords.remove(hostAddressEntryToPromote);
106
+ successRecords.append(hostAddressEntryToPromote);
107
+ }
108
+ }
109
+ }
110
+ getGoodRecords(address) {
111
+ return address.addressType === HostAddressType.AAAA ? this.aaaaRecords : this.aRecords;
112
+ }
113
+ getFailedRecords(address) {
114
+ return address.addressType === HostAddressType.AAAA ? this.failedAaaaRecords : this.failedARecords;
115
+ }
116
+ }
@@ -0,0 +1,23 @@
1
+ import { HostEntry } from "./HostEntry";
2
+ export class HostEntryTable {
3
+ constructor() {
4
+ this.map = new Map();
5
+ }
6
+ set(args, addresses, nextTimestampToProcessMs) {
7
+ const hostEntry = new HostEntry(nextTimestampToProcessMs);
8
+ hostEntry.updateRecords(addresses, nextTimestampToProcessMs);
9
+ this.map.set(args.hostName, hostEntry);
10
+ }
11
+ get(hostName) {
12
+ return this.map.get(hostName);
13
+ }
14
+ delete(hostName) {
15
+ this.map.delete(hostName);
16
+ }
17
+ clear() {
18
+ this.map.clear();
19
+ }
20
+ get size() {
21
+ return this.map.size;
22
+ }
23
+ }
@@ -0,0 +1,122 @@
1
+ import { HostAddress, HostResolverArguments } from "@aws-sdk/types";
2
+ /**
3
+ * DNS cache used by a {@link HostResolver} which maps:
4
+ * host name (string) -> {@link DnsCacheEntry}
5
+ * @internal
6
+ */
7
+ export interface DnsCache {
8
+ /**
9
+ * Maps a host name to a {@link DnsCacheEntry} in the cache
10
+ * @param args host resolver arguments which include host name
11
+ * @param addresses addresses to set into the cache entry
12
+ * @param nextTimestampToProcessMs timestamp for both cache entry processed and addresses' expiration
13
+ */
14
+ set(args: HostResolverArguments, addresses: HostAddress[], nextTimestampToProcessMs: number): void;
15
+ /**
16
+ * Gets the corresponding cache entry for hostName, otherwise undefined
17
+ * @param hostName key to get cache entry
18
+ */
19
+ get(hostName: string): DnsCacheEntry | undefined;
20
+ /**
21
+ * Deletes the corresponding cache entry for hostName
22
+ * @param hostName key to delete cache entry
23
+ */
24
+ delete(hostName: string): void;
25
+ /**
26
+ * Deletes all cache entries
27
+ */
28
+ clear(): void;
29
+ /**
30
+ * Gets the size of the cache
31
+ */
32
+ size: number;
33
+ }
34
+ /**
35
+ * Entry for a host name, mapped to in {@link DnsCache}
36
+ * @internal
37
+ */
38
+ export interface DnsCacheEntry {
39
+ /**
40
+ * Collection of good IPv6 records for an address
41
+ */
42
+ aaaaRecords: DnsCacheEntryCollection;
43
+ /**
44
+ * Collection of good IPv4 records for an address
45
+ */
46
+ aRecords: DnsCacheEntryCollection;
47
+ /**
48
+ * Collection of failed IPv6 records for an address
49
+ */
50
+ failedAaaaRecords: DnsCacheEntryCollection;
51
+ /**
52
+ * Collection of failed IPv4 records for an address
53
+ */
54
+ failedARecords: DnsCacheEntryCollection;
55
+ /**
56
+ * Timestamp for when to next process the entry
57
+ */
58
+ nextTimestampToUpdateMs: number;
59
+ /**
60
+ * Updates the entry given the new set of addresses and a new expiration
61
+ * timestamp:
62
+ * - If an address is already in the entry, the expiration timestamp is updated.
63
+ * - If an address is not in the entry, then it is added to the good records.
64
+ * @param addresses list of addresses used to update entry records
65
+ * @param expirationTtlMs expiration timestamp for new or updated addresses
66
+ */
67
+ updateRecords(addresses: HostAddress[], expirationTtlMs: number): void;
68
+ /**
69
+ * Removes expired records from records (except 1 in case of DNS outages).
70
+ * In the case that good records are empty, should attempt to promote 1
71
+ * non-expired failed address to the good records.
72
+ */
73
+ processRecords(): void;
74
+ /**
75
+ * Moves the address from good records to failed records
76
+ * @param address address to move to failed records
77
+ */
78
+ failAddressInRecords(address: HostAddress): void;
79
+ }
80
+ /**
81
+ * Collection which is used to manage {@link DnsCacheHostAddressEntry}
82
+ * @internal
83
+ */
84
+ export interface DnsCacheEntryCollection {
85
+ /**
86
+ * Get length / size of the collection
87
+ */
88
+ length: number;
89
+ /**
90
+ * Moves a {@link DnsCacheHostAddressEntry} from the beginning of the
91
+ * collection to the end of itself, and returns the host address entry
92
+ * that was cycled.
93
+ * If another collection is provided, it will move the host address entry
94
+ * to the end of that collection.
95
+ * @param collection optional collection to cycle to.
96
+ */
97
+ cycle(collection?: DnsCacheEntryCollection): DnsCacheHostAddressEntry;
98
+ /**
99
+ * Appends an element to the end of the collection
100
+ * @param element element to append to the collection
101
+ */
102
+ append(element: DnsCacheHostAddressEntry): void;
103
+ /**
104
+ * Removes an element from the collection, and returns that element.
105
+ * @param element element to remove and return from the collection
106
+ */
107
+ remove(element: DnsCacheHostAddressEntry): DnsCacheHostAddressEntry;
108
+ /**
109
+ * Implements iterator for the collection
110
+ */
111
+ [Symbol.iterator](): Iterator<DnsCacheHostAddressEntry>;
112
+ }
113
+ /**
114
+ * Captures cached {@link HostAddress} information
115
+ * @internal
116
+ */
117
+ export interface DnsCacheHostAddressEntry extends HostAddress {
118
+ /**
119
+ * Timestamp for when the entry expires
120
+ */
121
+ expirationTtlMs: number;
122
+ }
@@ -1,16 +1,103 @@
1
+ /// <reference types="node" />
1
2
  import { HostAddress, HostResolver as IHostResolver, HostResolverArguments } from "@aws-sdk/types";
3
+ import { LookupAddress, LookupAllOptions } from "dns";
4
+ import { DnsCache } from "./DnsCache";
5
+ /**
6
+ * Node.js dns.lookup() function type used in {@link NodeDnsLookupHostResolver}
7
+ * @internal
8
+ */
9
+ interface NodeDnsLookupFn {
10
+ (hostname: string, options: LookupAllOptions): Promise<LookupAddress[]>;
11
+ }
12
+ /**
13
+ * Configuration options for {@link NodeDnsLookupHostResolver}
14
+ * @internal
15
+ */
16
+ export interface NodeDnsLookupHostResolverConfig {
17
+ /**
18
+ * Cache for holding address entries
19
+ * @internal
20
+ */
21
+ cache?: DnsCache;
22
+ /**
23
+ * TTL offset in MS when generating expiration TTLs
24
+ * @internal
25
+ */
26
+ ttlMs?: number;
27
+ /**
28
+ * Pluggable Node.js dns.lookup() function
29
+ * @internal
30
+ */
31
+ nodeDnsLookup?: NodeDnsLookupFn;
32
+ }
2
33
  /**
3
34
  * {@link HostResolver} implementation that uses the Node.js dns.lookup() API.
4
- * TODO(dns): implement cache
5
35
  */
6
36
  export declare class NodeDnsLookupHostResolver implements IHostResolver {
7
37
  /**
8
- * Node.js resolveAddress() using the dns.lookup() APIs.
9
- * @see https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options
38
+ * Default TTL in ms
39
+ * @internal
40
+ */
41
+ private static DEFAULT_TTL_MS;
42
+ /**
43
+ * Provider that returns a default {@link DnsCache}
44
+ * @returns DnsCache implementation
45
+ * @internal
46
+ */
47
+ private static createDefaultCacheProvider;
48
+ /**
49
+ * Default Node.js dns.lookup() function
50
+ * @internal
51
+ */
52
+ private static DEFAULT_NODE_DNS_LOOKUP;
53
+ /**
54
+ * TTL in ms
55
+ * @internal
56
+ */
57
+ private ttlMs;
58
+ /**
59
+ * {@link DnsCache} which maps {@link HostAddress.hostName} to {@link DnsCacheEntry}
60
+ * @internal
61
+ */
62
+ private cache;
63
+ /**
64
+ * Node.js dns.lookup() API implementation
65
+ * @internal
66
+ */
67
+ private nodeDnsLookup;
68
+ constructor({ ttlMs, cache, nodeDnsLookup, }?: NodeDnsLookupHostResolverConfig);
69
+ /**
70
+ * Resolves the address(es) for {@link HostResolverArguments} and returns a
71
+ * list of addresses with (most likely) two addresses, one {@link HostAddressType.AAAA}
72
+ * and one {@link HostAddressType.A}. Calls to this function will likely alter
73
+ * the cache so that if there's multiple addresses, a different set will be
74
+ * returned on the next call.
75
+ * In the case of multi-answer, still only a maximum of two records should be
76
+ * returned.
10
77
  * @param args arguments with host name query addresses for
11
78
  * @returns promise with a list of {@link HostAddress}
79
+ * @see https://github.com/awslabs/aws-c-io/blob/f2ff573c191e1c4ea0248af5c08161356be3bc78/source/host_resolver.c#L964
12
80
  */
13
81
  resolveAddress(args: HostResolverArguments): Promise<HostAddress[]>;
82
+ /**
83
+ * Reports a failure on a {@link HostAddress} so that the cache can
84
+ * accomodate the failure and likely not return the address until it recovers.
85
+ * @param addr host address to report a failure on
86
+ */
14
87
  reportFailureOnAddress(addr: HostAddress): void;
88
+ /**
89
+ * Empties the cache for a {@link HostResolverArguments.hostName}.
90
+ * If {@link HostResolverArguments.hostName} is not provided, the cache
91
+ * is emptied for all host names.
92
+ * @param args optional arguments to empty the cache for
93
+ */
15
94
  purgeCache(args?: HostResolverArguments): void;
95
+ /**
96
+ * Node.js resolveAddress() using the dns.lookup() APIs.
97
+ * @see https://nodejs.org/api/dns.html#dnspromiseslookuphostname-options
98
+ * @param args arguments with host name query addresses for
99
+ * @returns promise with a list of {@link HostAddress}
100
+ */
101
+ private nodeDnsLookupResolveAddress;
16
102
  }
103
+ export {};
@@ -0,0 +1,32 @@
1
+ import { HostAddress, HostResolverArguments } from "@aws-sdk/types";
2
+ export interface DnsCache {
3
+ set(
4
+ args: HostResolverArguments,
5
+ addresses: HostAddress[],
6
+ nextTimestampToProcessMs: number
7
+ ): void;
8
+ get(hostName: string): DnsCacheEntry | undefined;
9
+ delete(hostName: string): void;
10
+ clear(): void;
11
+ size: number;
12
+ }
13
+ export interface DnsCacheEntry {
14
+ aaaaRecords: DnsCacheEntryCollection;
15
+ aRecords: DnsCacheEntryCollection;
16
+ failedAaaaRecords: DnsCacheEntryCollection;
17
+ failedARecords: DnsCacheEntryCollection;
18
+ nextTimestampToUpdateMs: number;
19
+ updateRecords(addresses: HostAddress[], expirationTtlMs: number): void;
20
+ processRecords(): void;
21
+ failAddressInRecords(address: HostAddress): void;
22
+ }
23
+ export interface DnsCacheEntryCollection {
24
+ length: number;
25
+ cycle(collection?: DnsCacheEntryCollection): DnsCacheHostAddressEntry;
26
+ append(element: DnsCacheHostAddressEntry): void;
27
+ remove(element: DnsCacheHostAddressEntry): DnsCacheHostAddressEntry;
28
+ [Symbol.iterator](): Iterator<DnsCacheHostAddressEntry>;
29
+ }
30
+ export interface DnsCacheHostAddressEntry extends HostAddress {
31
+ expirationTtlMs: number;
32
+ }
@@ -3,8 +3,31 @@ import {
3
3
  HostResolver as IHostResolver,
4
4
  HostResolverArguments,
5
5
  } from "@aws-sdk/types";
6
+ import { LookupAddress, LookupAllOptions } from "dns";
7
+ import { DnsCache } from "./DnsCache";
8
+ interface NodeDnsLookupFn {
9
+ (hostname: string, options: LookupAllOptions): Promise<LookupAddress[]>;
10
+ }
11
+ export interface NodeDnsLookupHostResolverConfig {
12
+ cache?: DnsCache;
13
+ ttlMs?: number;
14
+ nodeDnsLookup?: NodeDnsLookupFn;
15
+ }
6
16
  export declare class NodeDnsLookupHostResolver implements IHostResolver {
17
+ private static DEFAULT_TTL_MS;
18
+ private static createDefaultCacheProvider;
19
+ private static DEFAULT_NODE_DNS_LOOKUP;
20
+ private ttlMs;
21
+ private cache;
22
+ private nodeDnsLookup;
23
+ constructor({
24
+ ttlMs,
25
+ cache,
26
+ nodeDnsLookup,
27
+ }?: NodeDnsLookupHostResolverConfig);
7
28
  resolveAddress(args: HostResolverArguments): Promise<HostAddress[]>;
8
29
  reportFailureOnAddress(addr: HostAddress): void;
9
30
  purgeCache(args?: HostResolverArguments): void;
31
+ private nodeDnsLookupResolveAddress;
10
32
  }
33
+ export {};
@@ -0,0 +1,12 @@
1
+ import { DnsCacheEntryCollection } from "../DnsCache";
2
+ import { HostAddressEntry } from "./HostEntry";
3
+ export declare class HostAddressEntryCollection
4
+ implements DnsCacheEntryCollection
5
+ {
6
+ data: HostAddressEntry[];
7
+ readonly length: number;
8
+ cycle(collection?: HostAddressEntryCollection): HostAddressEntry;
9
+ append(entry: HostAddressEntry): void;
10
+ remove(entry: HostAddressEntry): HostAddressEntry;
11
+ [Symbol.iterator](): Iterator<HostAddressEntry>;
12
+ }
@@ -0,0 +1,19 @@
1
+ import { HostAddress } from "@aws-sdk/types";
2
+ import { DnsCacheEntry, DnsCacheHostAddressEntry } from "../DnsCache";
3
+ import { HostAddressEntryCollection } from "./HostAddressEntryCollection";
4
+ export interface HostAddressEntry extends DnsCacheHostAddressEntry {}
5
+ export declare class HostEntry implements DnsCacheEntry {
6
+ aaaaRecords: HostAddressEntryCollection;
7
+ aRecords: HostAddressEntryCollection;
8
+ failedAaaaRecords: HostAddressEntryCollection;
9
+ failedARecords: HostAddressEntryCollection;
10
+ nextTimestampToUpdateMs: number;
11
+ constructor(nextTimestampToProcessMs: number);
12
+ updateRecords(addresses: HostAddress[], expirationTtlMs: number): void;
13
+ processRecords(): void;
14
+ failAddressInRecords(address: HostAddress): void;
15
+ private findAddress;
16
+ private processRecordsAddressType;
17
+ private getGoodRecords;
18
+ private getFailedRecords;
19
+ }
@@ -0,0 +1,16 @@
1
+ import { HostAddress, HostResolverArguments } from "@aws-sdk/types";
2
+ import { DnsCache } from "../DnsCache";
3
+ import { HostEntry } from "./HostEntry";
4
+ export declare class HostEntryTable implements DnsCache {
5
+ private map;
6
+ constructor();
7
+ set(
8
+ args: HostResolverArguments,
9
+ addresses: HostAddress[],
10
+ nextTimestampToProcessMs: number
11
+ ): void;
12
+ get(hostName: string): HostEntry | undefined;
13
+ delete(hostName: string): void;
14
+ clear(): void;
15
+ readonly size: number;
16
+ }
@@ -0,0 +1,29 @@
1
+ import { DnsCacheEntryCollection } from "../DnsCache";
2
+ import { HostAddressEntry } from "./HostEntry";
3
+ /**
4
+ * Collection using an Array as the underlying data structure
5
+ * @internal
6
+ */
7
+ export declare class HostAddressEntryCollection implements DnsCacheEntryCollection {
8
+ data: HostAddressEntry[];
9
+ get length(): number;
10
+ /**
11
+ * Removes and appends the first entry to the end, e.g. 2 cycles:
12
+ * [A, B, C] -> [B, C, A] -> [C, A, B]
13
+ * @param collection optional collection to append the cycled entry to
14
+ * @returns entry cycled
15
+ */
16
+ cycle(collection?: HostAddressEntryCollection): HostAddressEntry;
17
+ /**
18
+ * Adds entry to the end of the collection.
19
+ * @param entry entry to add
20
+ */
21
+ append(entry: HostAddressEntry): void;
22
+ /**
23
+ * Removes an entry from the collection
24
+ * @param entry entry to remove
25
+ * @returns the removed entry
26
+ */
27
+ remove(entry: HostAddressEntry): HostAddressEntry;
28
+ [Symbol.iterator](): Iterator<HostAddressEntry>;
29
+ }
@@ -0,0 +1,87 @@
1
+ import { HostAddress } from "@aws-sdk/types";
2
+ import { DnsCacheEntry, DnsCacheHostAddressEntry } from "../DnsCache";
3
+ import { HostAddressEntryCollection } from "./HostAddressEntryCollection";
4
+ /**
5
+ * Interface used in {@link HostEntry} records
6
+ * @internal
7
+ */
8
+ export interface HostAddressEntry extends DnsCacheHostAddressEntry {
9
+ }
10
+ /**
11
+ * {@link DnsCacheEntry} implementation that uses {@link LinkedList} for
12
+ * {@link DnsCacheEntryCollection}.
13
+ * @internal
14
+ */
15
+ export declare class HostEntry implements DnsCacheEntry {
16
+ /**
17
+ * {@link LinkedList} of good IPv6 records for an address
18
+ */
19
+ aaaaRecords: HostAddressEntryCollection;
20
+ /**
21
+ * {@link LinkedList} of good IPv4 records for an address
22
+ */
23
+ aRecords: HostAddressEntryCollection;
24
+ /**
25
+ * {@link LinkedList} of failed IPv6 records for an address
26
+ */
27
+ failedAaaaRecords: HostAddressEntryCollection;
28
+ /**
29
+ * {@link LinkedList} of failed IPv4 records for an address
30
+ */
31
+ failedARecords: HostAddressEntryCollection;
32
+ /**
33
+ * Timestamp for when to next process the entry
34
+ */
35
+ nextTimestampToUpdateMs: number;
36
+ constructor(nextTimestampToProcessMs: number);
37
+ /**
38
+ * Updates the entry given the new set of addresses and a new expiration
39
+ * timestamp:
40
+ * - If an address is already in the entry, the expiration timestamp is updated.
41
+ * - If an address is not in the entry, then it is added to the good records.
42
+ * @param addresses list of addresses used to update entry records
43
+ * @param expirationTtlMs expiration timestamp for new or updated addresses
44
+ * @see https://github.com/awslabs/aws-c-io/blob/f2ff573c191e1c4ea0248af5c08161356be3bc78/source/host_resolver.c#L703
45
+ */
46
+ updateRecords(addresses: HostAddress[], expirationTtlMs: number): void;
47
+ /**
48
+ * Removes expired records from records (except 1 in case of DNS outages).
49
+ * In the case that good records are empty, should attempt to promote 1
50
+ * non-expired failed address to the good records.
51
+ * Uses {@link processRecordsAddressType} for each {@link HostAddressType}
52
+ * @see https://github.com/awslabs/aws-c-io/blob/f2ff573c191e1c4ea0248af5c08161356be3bc78/source/host_resolver.c#L475
53
+ */
54
+ processRecords(): void;
55
+ /**
56
+ * Moves the address from good records to failed records
57
+ * @param address address to move to failed records
58
+ */
59
+ failAddressInRecords(address: HostAddress): void;
60
+ /**
61
+ * Find the corresponding {@link HostAddressEntry} for a {@link HostAddress} in
62
+ * a {@link HostEntry}'s records, or return undefined.
63
+ * @param address address to search for
64
+ * @returns host address entry if found, otherwise undefined
65
+ */
66
+ private findAddress;
67
+ /**
68
+ * Removes expired records from records (except 1 in case of DNS outages).
69
+ * In the case that good records are empty, should attempt to promote 1
70
+ * non-expired failed address to the good records.
71
+ * @param successRecords good records in cache to update
72
+ * @param failedRecords bad records in cache to update
73
+ */
74
+ private processRecordsAddressType;
75
+ /**
76
+ * Gets the good records for an address based on {@link HostAddressType}
77
+ * @param address address used to get the {@link HostAddressType}
78
+ * @returns good records for an address
79
+ */
80
+ private getGoodRecords;
81
+ /**
82
+ * Gets the failed records for an address based on {@link HostAddressType}
83
+ * @param address address used to get the {@link HostAddressType}
84
+ * @returns failed records for an address
85
+ */
86
+ private getFailedRecords;
87
+ }
@@ -0,0 +1,41 @@
1
+ import { HostAddress, HostResolverArguments } from "@aws-sdk/types";
2
+ import { DnsCache } from "../DnsCache";
3
+ import { HostEntry } from "./HostEntry";
4
+ /**
5
+ * Host entry table which implements {@link DnsCache}, mapping:
6
+ * host name (string) -> {@link HostEntry}
7
+ * @internal
8
+ */
9
+ export declare class HostEntryTable implements DnsCache {
10
+ /**
11
+ * Internal map as the host name level cache
12
+ */
13
+ private map;
14
+ constructor();
15
+ /**
16
+ * Maps a host name to a {@link HostEntry} in the cache, and uses
17
+ * {@link HostEntry.updateRecords} to initialize address records.
18
+ * @param args host resolver arguments which include host name
19
+ * @param addresses addresses to set into the cache entry
20
+ * @param nextTimestampToProcessMs timestamp for both cache entry processed and addresses' expiration
21
+ */
22
+ set(args: HostResolverArguments, addresses: HostAddress[], nextTimestampToProcessMs: number): void;
23
+ /**
24
+ * Gets the corresponding {@link HostEntry} for hostName, otherwise undefined
25
+ * @param hostName key to get cache entry
26
+ */
27
+ get(hostName: string): HostEntry | undefined;
28
+ /**
29
+ * Deletes the corresponding {@link HostEntry} for hostName
30
+ * @param hostName key to delete cache entry
31
+ */
32
+ delete(hostName: string): void;
33
+ /**
34
+ * Deletes all {@link HostEntry} mappings
35
+ */
36
+ clear(): void;
37
+ /**
38
+ * Gets the size of the cache
39
+ */
40
+ get size(): number;
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-sdk/util-dns",
3
- "version": "3.271.0",
3
+ "version": "3.274.0",
4
4
  "description": "Implementations of DNS host resolvers.",
5
5
  "main": "./dist-cjs/index.js",
6
6
  "module": "./dist-es/index.js",
@@ -26,7 +26,7 @@
26
26
  "tslib": "^2.3.1"
27
27
  },
28
28
  "devDependencies": {
29
- "@aws-sdk/types": "3.271.0",
29
+ "@aws-sdk/types": "3.272.0",
30
30
  "@tsconfig/recommended": "1.0.1",
31
31
  "@types/node": "^14.14.31",
32
32
  "concurrently": "7.0.0",