zgrzyt 2.1.0 → 2.2.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.
package/Readme.md CHANGED
@@ -36,6 +36,7 @@ zgrzyt --force
36
36
  ; list of potential servers configured for this domain CNAME record
37
37
  servers[]=alfa.example.net
38
38
  servers[]=beta.example.net
39
+ ipv6=true ; by default ipv4 is true and ipv6 is false
39
40
 
40
41
  [cloudflare]
41
42
  ; cloudflare API token
@@ -51,7 +52,7 @@ method=HEAD ; optional, HTTP method used by zgrzyt
51
52
  ```
52
53
 
53
54
  If `api.domain` is not specified its value is deduced from `api.url`.
54
- If `api.method` is not specified zgrzyt will send `HEAD` requests to poll the servers. `api.method` can be set to `GET` if needed. `retry` and `timeout` can be either configured globally or separately for each API.
55
+ If `api.method` is not specified zgrzyt will send `HEAD` requests to poll the servers. `api.method` can be set to `GET` if needed. `retry`, `timeout`, `ipv4`, and `ipv6` can be either configured globally or separately for each API.
55
56
 
56
57
  ### Multiple checks
57
58
 
package/lib/check.js CHANGED
@@ -23,25 +23,39 @@ export {
23
23
  };
24
24
 
25
25
  async function checkService(server, api) {
26
- const address = await resolve(server);
27
- debug('Resolved %s to %s', server, address);
28
- const ok = await checkApi(api, address);
26
+ const { ipv4 = true, ipv6 = false } = api;
27
+ const addresses = await resolve(server, { ipv4, ipv6 });
28
+ if (debug.enabled) {
29
+ debug('Resolved %s to %s', server, addresses.map(a => a.address).join(', '));
30
+ }
31
+ const oks = await Promise.all(addresses.map(a => checkApi(api, a)));
32
+ // collate results
33
+ const ok = oks.every(ok => ok);
29
34
  const health = await updateHealth(api, server, ok);
30
35
  return {
31
- address,
36
+ address: addresses[0].address,
32
37
  server,
33
38
  health,
34
39
  ok
35
40
  };
36
41
  }
37
42
 
38
- async function checkApi({ url, timeout, method = 'HEAD', headers = {}, retry = 2, family = 4 }, address) {
43
+ async function checkApi(api, { address, family }) {
44
+
45
+ const {
46
+ url,
47
+ method = 'HEAD',
48
+ timeout = 5000,
49
+ headers = {}
50
+ } = api;
51
+
39
52
  debug('Checking %s on %s with timeout %dms', url, address, timeout);
40
53
 
41
54
  const { protocol } = new URL(url);
42
55
  const { request } = 'https:' === protocol ? https : http;
43
56
 
44
57
  let result;
58
+ let retry = api.retry || 3;
45
59
  do {
46
60
  result = await makeRequest();
47
61
  if (result) {
@@ -54,15 +68,15 @@ async function checkApi({ url, timeout, method = 'HEAD', headers = {}, retry = 2
54
68
 
55
69
  function makeRequest() {
56
70
  return new Promise(resolve => request(url, {
57
- method,
58
- family,
59
- lookup,
60
- timeout,
61
- headers: {
62
- 'User-Agent': USER_AGENT,
63
- ...headers
64
- }
65
- })
71
+ method,
72
+ family,
73
+ lookup,
74
+ timeout,
75
+ headers: {
76
+ 'User-Agent': USER_AGENT,
77
+ ...headers
78
+ }
79
+ })
66
80
  .on('timeout', function () {
67
81
  debug('Timeout for %s on %s', url, address);
68
82
  this.destroy(new Error('Request timeout.'));
@@ -82,7 +96,11 @@ async function checkApi({ url, timeout, method = 'HEAD', headers = {}, retry = 2
82
96
  }
83
97
 
84
98
  function lookup(domain, options, fn) {
85
- fn(null, address, family);
99
+ debug('lookup', domain, address, options);
100
+ if (options.all) {
101
+ address = [address];
102
+ }
103
+ setImmediate(fn, null, address, options.family);
86
104
  }
87
105
  }
88
106
 
package/lib/config.js CHANGED
@@ -21,10 +21,16 @@ function prepareConfig(config) {
21
21
  cluster = {}
22
22
  } = config;
23
23
 
24
- if (!cloudflare || !cloudflare.token) {
24
+ if (!cloudflare?.token) {
25
25
  console.error('Cloudflare API token not configured');
26
26
  return;
27
27
  }
28
+ if (typeof cloudflare.timeout === 'string') {
29
+ cloudflare.timeout = parseInt(cloudflare.timeout);
30
+ }
31
+ if (typeof cloudflare.retry === 'string') {
32
+ cloudflare.retry = parseInt(cloudflare.retry);
33
+ }
28
34
 
29
35
  // collect all APIs
30
36
  const apis = [
@@ -47,6 +53,8 @@ function prepareConfig(config) {
47
53
  const retry = parseInt(api.retry || config.retry, 10) || 2;
48
54
  const force = 'force' in api ? api.force : config.force;
49
55
  const repair = parseInt(api.repair || config.repair, 10) || 5;
56
+ const ipv6 = Boolean(api.ipv6 ?? config.ipv6);
57
+ const ipv4 = Boolean(api.ipv4 ?? config.ipv4 ?? true);
50
58
 
51
59
  const { servers } = api.cluster in cluster ? cluster[api.cluster] : config;
52
60
  if (!servers || !servers.length) {
@@ -72,6 +80,8 @@ function prepareConfig(config) {
72
80
  timeout,
73
81
  retry,
74
82
  domain,
83
+ ipv4,
84
+ ipv6,
75
85
  zone
76
86
  },
77
87
  client,
package/lib/dns.js CHANGED
@@ -1,22 +1,48 @@
1
1
  import { Resolver } from 'node:dns/promises';
2
2
  import makeDebug from 'debug';
3
3
 
4
- const resolver = new Resolver();
5
4
  const debug = makeDebug('dns:zgrzyt');
6
5
 
7
6
  const cache = Object.create(null);
8
7
 
9
- export function resolve(domain) {
8
+ const resolver = new Resolver();
9
+
10
+ /**
11
+ * Resolve domain to IP addresses
12
+ *
13
+ * @param {String} domain
14
+ * @param { ipv4, ipv6 } type of addresses to resolve
15
+ * @returns {Promise<Array<{address: String, family: String}>>} list of resolved addresses
16
+ */
17
+ export async function resolve(domain, { ipv4, ipv6 }) {
10
18
  let p = cache[domain];
11
19
  if (!p) {
12
20
  cache[domain] = p = doResolve(domain);
13
21
  }
14
- return p;
22
+ const addresses = await p;
23
+ return addresses.filter(
24
+ a => (a.family === 4 && ipv4) || (a.family === 6 && ipv6)
25
+ );
15
26
  }
16
27
 
28
+ const RRTYPE_TO_FAMILY = {
29
+ A: 4,
30
+ AAAA: 6
31
+ };
32
+
17
33
  async function doResolve(domain) {
18
34
  debug('Resolving %s', domain);
19
- // const r6 = await resolver.resolve6(domain);
20
- const [addr4] = await resolver.resolve4(domain);
21
- return addr4;
35
+
36
+ const addresses = await Promise.all(['A', 'AAAA'].map(resolveWithFamily));
37
+ return addresses.flat();
38
+
39
+ async function resolveWithFamily(rrtype) {
40
+ try {
41
+ const addresses = await resolver.resolve(domain, rrtype);
42
+ return addresses.map(address => ({ address, family: RRTYPE_TO_FAMILY[rrtype] }));
43
+ } catch (error) {
44
+ debug('Failed to resolve %s with type %s: %s', domain, rrtype, error);
45
+ return [];
46
+ }
47
+ }
22
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zgrzyt",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Poor man's load balancing DNS switcher.",
5
5
  "type": "module",
6
6
  "author": {
@@ -27,8 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "debug": "~4",
30
- "got": "~13",
31
- "parse-domain": "~7",
30
+ "got": "~14",
31
+ "parse-domain": "~8",
32
32
  "rc": "^1.2.8",
33
33
  "sprintfjs": "^1.2.16"
34
34
  },