diff --git a/.codeclimate.yml b/.codeclimate.yml index d44ff41..de2068e 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,13 +1,13 @@ engines: eslint: enabled: true - channel: "eslint-8" + channel: 'eslint-8' config: - config: ".eslintrc.yaml" + config: '.eslintrc.yaml' ratings: - paths: - - "**.js" + paths: + - '**.js' checks: return-statements: diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 6313e7c..035a400 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -2,16 +2,6 @@ env: node: true es6: true mocha: true - es2020: true + es2022: true -plugins: - - haraka - -extends: - - eslint:recommended - - plugin:haraka/recommended - -root: true - -rules: - indent: [2, 2, { SwitchCase: 1 } ] \ No newline at end of file +extends: ['@haraka'] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c05ac3b..b969775 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,10 @@ version: 2 updates: - - package-ecosystem: "npm" + - package-ecosystem: 'npm' # Look for `package.json` and `lock` files in the `root` directory - directory: "/" + directory: '/' # Check the npm registry for updates every day (weekdays) schedule: - interval: "monthly" + interval: 'monthly' allow: - - dependency-type: "production" \ No newline at end of file + - dependency-type: 'production' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 752f845..7034b75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: [ pull_request, push ] +on: [pull_request, push] env: CI: true @@ -14,9 +14,9 @@ jobs: secrets: inherit test: - needs: [ lint ] + needs: [lint] uses: haraka/.github/.github/workflows/ubuntu.yml@master windows: - needs: [ lint ] + needs: [lint] uses: haraka/.github/.github/workflows/windows.yml@master diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3627451..8314a66 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,10 +2,10 @@ name: CodeQL on: push: - branches: [ master ] + branches: [master] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [master] schedule: - cron: '18 7 * * 4' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d489fbd..e81c15f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,4 +13,4 @@ env: jobs: publish: uses: haraka/.github/.github/workflows/publish.yml@master - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/.npmignore b/.npmignore deleted file mode 100644 index fa00e1b..0000000 --- a/.npmignore +++ /dev/null @@ -1,59 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -package-lock.json -bower_components -# Optional npm cache directory -.npmrc -.idea -.DS_Store -haraka-update.sh - -.github -.release -.codeclimate.yml -.editorconfig -.gitignore -.gitmodules -.lgtm.yml -appveyor.yml -codecov.yml -.travis.yml -.eslintrc.yaml -.eslintrc.json -test \ No newline at end of file diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..8ded5e0 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,2 @@ +singleQuote: true +semi: false diff --git a/.release b/.release index 954197d..36bb27a 160000 --- a/.release +++ b/.release @@ -1 +1 @@ -Subproject commit 954197dae07b32c4476ff87ec9ae7371311ec97d +Subproject commit 36bb27a93862517943e04f24fd67b0df2da6cbbe diff --git a/Changes.md b/CHANGELOG.md similarity index 68% rename from Changes.md rename to CHANGELOG.md index 7a3383c..761abc0 100644 --- a/Changes.md +++ b/CHANGELOG.md @@ -1,39 +1,50 @@ +# Changelog -#### N.N.N - YYYY-MM-DD +The format is based on [Keep a Changelog](https://keepachangelog.com/). + +### Unreleased + +### [1.6.0] - 2024-04-17 + +- feat: normalizeDomain, for punycode/IDN names +- feat: get_mx now _also_ returns implicit MX records +- feat: added get_implicit_mx +- feat: added resolve_mx_hosts +- doc(Changes): fixed broken tag version links +- doc(CONTRIBUTORS): added +- chore: populate [files] in package.json. Delete .npmignore +- chore(lint): remove duplicate / stale rules from .eslintrc +- dep(punycode): punycode -> punycode.js (avoid name collission) +- chore: refactored get_ips_by_host using Promise.allSettled ### [1.5.4] - 2024-04-02 - Add timeout to DNS Resolver (#83) - ### [1.5.3] - 2023-12-15 - dep(punycode): override built-in with trailing / - ### [1.5.2] - 2023-12-11 - dep(stun): use updated @msimerson/stun - ### [1.5.1] - 2023-12-03 - feat(is_local_host): also match when - - the mx dest is a hostname that matches our hostname - - the mx dest matches our public IP (may not be locally bound) + - the mx dest is a hostname that matches our hostname + - the mx dest matches our public IP (may not be locally bound) - bump dep versions #78 - test update for node v20 #78 - ci: enable CI tests on PRs #77 - test: increase DNS timeouts from 3s to 5s #77 - ### [1.5.0] - 2022-12-20 - feat: add async support for get_public_ip #75 - dep: replace vs-stun with stun - doc: use async/await syntax in examples #74 - ### [1.4.1] - 2022-07-22 - feat(get_mx): use async/await @@ -41,137 +52,113 @@ - test(get_mx): expand and improve test coverage - chore(ci): use more shared haraka/.github workflows - ### [1.3.7] - 2022-06-03 - ci: fix the dependabot allow syntax - ### [1.3.6] - 2022-06-01 - chore: replace .release with submodule - chore(ci): populate test matrix with Node.js LTS versions - chore(ci): limit dependabot updates to production deps - -#### 1.3.5 - 2022-05-27 +#### [1.3.5] - 2022-05-27 - chore(ci): use shared GHA workflows - style(es6): use dns.promises internally - dep(async): replace async dependency with Promise.all - doc(README): use code fences around examples (vs indention) - -#### 1.3.4 - 2022-01-05 +#### [1.3.4] - 2022-01-05 - promisify get_ips_by_host (backwards compatible) - -#### 1.3.3 - 2020-01-05 +#### [1.3.3] - 2020-01-05 - refactored is_local_host function to return a promise instead of using a callback #65 - -#### 1.3.2 - 2021-12-20 +#### [1.3.2] - 2021-12-20 - add is_local_host function #63 - -#### 1.3.1 - 2021-10-13 +#### [1.3.1] - 2021-10-13 - get_mx: wrap dns.resolveMx in a try haraka/Haraka#2985 - add .release scripts - add GH workflow, publish release to NPM upon merge to master - #### 1.3.0 - 2021-01-23 - Support passing an array to ip_in_list #60 - #### 1.2.4 - 2021-01-14 - add "any" IP to is_local_ip - add TEST-NET-[1-3] to is_private_ip - #### 1.2.3 - 2020-12-19 - fix: restore the tests wrapping the resolveMX iterable - #### 1.2.2 - 2020-12-15 - get_mx: do not include implicit MX - -#### 1.2.1 - 2020-11-17 +#### [1.2.1] - 2020-11-17 - bump ipaddr.js to 2.0.0 #56 - -#### 1.2.0 - 2020-06-23 +#### [1.2.0] - 2020-06-23 - added get_mx - remove deprecated load_tls_ini - remove deprecated tls_ini_section_with_defaults - #### 1.1.5 - 2020-04-11 - ipv6_bogus: handle parsing broken ipv6 addresses #49 - update async to version 3.0.1 #43 - #### 1.1.4 - 2019-04-04 - stop is_private_ip from checking if the IP is bound to a local network interface - #### 1.1.3 - 2019-03-01 - is_local_ip checks local network interfaces too - #### 1.1.2 - 2018-11-03 - add is_local_ip - #### 1.1.1 - 2018-07-19 - ip_in_list doesn't throw on empty list - #### 1.1.0 - 2018-04-11 - add get_primary_host_name haraka/Haraka#2380 - #### 1.0.14 - 2018-01-25 - restore tls_ini_section_with_defaults function (deprecated since Haraka 2.0.17) - #### 1.0.13 - 2018-01-19 - get_public_ip: assign timer before calling connect #29 - - avoid race where timeout isn't cleared because stun connect errors immediately + - avoid race where timeout isn't cleared because stun connect errors immediately - remove TLS functions that have been subsumed into Haraka/tls_socket: tls_ini_section_with_defaults, parse_x509_names, parse_x509_expire, parse_x509, load_tls_dir - convert concatenated strings to template literals #28 - eslint updates #25, #27 - improved x509 parser #22 - #### 1.0.10 - 2017-07-27 - added vs-stun as optional dep (from Haraka) #21 - #### 1.0.9 - 2017-06-16 -- lint fixes for compat with eslint 4 #18 - +- lint fixes for compat with eslint 4 #18 #### 1.0.8 - 2017-03-08 @@ -180,39 +167,42 @@ - rename certs -> cert (be consistent with haraka/plugins/tls) - store cert/key as buffers (was strings) - #### 1.0.7 - 2017-03-08 - handle undefined tls.ini section - #### 1.0.6 - 2017-03-04 - add tls_ini_section_with_defaults() - add load_tls_dir() - add parse_x509_names() - #### 1.0.5 - 2016-11-20 -* add enableSNI TLS option - +- add enableSNI TLS option #### 1.0.4 - 2016-10-25 -* initialize TLS opts in (section != main) as booleans - +- initialize TLS opts in (section != main) as booleans #### 1.0.3 - 2016-10-25 -* added tls.ini loading - +- added tls.ini loading +[1.2.0]: https://github.com/haraka/haraka-net-utils/releases/tag/1.2.0 +[1.2.1]: https://github.com/haraka/haraka-net-utils/releases/tag/1.2.1 +[1.3.1]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.1 +[1.3.2]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.2 +[1.3.3]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.3 +[1.3.4]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.4 +[1.3.5]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.5 [1.3.6]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.6 -[1.3.7]: https://github.com/haraka/haraka-net-utils/releases/tag/1.3.7 -[1.4.1]: https://github.com/haraka/haraka-net-utils/releases/tag/1.4.1 -[1.5.0]: https://github.com/haraka/haraka-net-utils/releases/tag/1.5.0 -[1.5.1]: https://github.com/haraka/haraka-net-utils/releases/tag/1.5.1 -[1.5.2]: https://github.com/haraka/haraka-net-utils/releases/tag/1.5.2 -[1.5.3]: https://github.com/haraka/haraka-net-utils/releases/tag/1.5.3 -[1.5.4]: https://github.com/haraka/haraka-net-utils/releases/tag/1.5.4 +[1.3.7]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.3.7 +[1.4.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.4.0 +[1.4.1]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.4.1 +[1.5.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.0 +[1.5.1]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.1 +[1.5.2]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.2 +[1.5.3]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.3 +[1.5.4]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.5.4 +[1.6.0]: https://github.com/haraka/haraka-net-utils/releases/tag/v1.6.0 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..ac187dd --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,8 @@ +# Contributors + +This handcrafted artisinal software is brought to you by: + +|
msimerson (57) |
baudehlo (4) |
DoobleD (2) |
lnedry (2) |
Juerd (1) |
olsonpm (1) |
typingArtist (1) | +| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | + +this file is maintained by [.release](https://github.com/msimerson/.release) diff --git a/README.md b/README.md index 639fab3..96ee3d8 100644 --- a/README.md +++ b/README.md @@ -16,56 +16,56 @@ This module provides network utility functions. ```js // Convert IPv4 to long -const long = net_utils.ip_to_long('11.22.33.44'); // 185999660 +const long = net_utils.ip_to_long('11.22.33.44') // 185999660 ``` ### long_to_ip ```js // Convert long to IPv4 -const ip = net_utils.long_to_ip(185999660); // 11.22.33.44 +const ip = net_utils.long_to_ip(185999660) // 11.22.33.44 ``` ### dec_to_hex ```js // Convert decimal to hex -const hex = net_utils.dec_to_hex(20111104); // 132df00 +const hex = net_utils.dec_to_hex(20111104) // 132df00 ``` ### hex_to_dec ```js // Convert hex to decimal -const dec = net_utils.hex_to_dec('132df00'); // 20111104 +const dec = net_utils.hex_to_dec('132df00') // 20111104 ``` ### is_local_ipv4 ```js // Is IPv4 address on a local network? -net_utils.is_local_ipv4('127.0.0.200'); // true (localhost) -net_utils.is_local_ipv4('169.254.0.0'); // true (link local) -net_utils.is_local_ipv4('226.0.0.1'); // false +net_utils.is_local_ipv4('127.0.0.200') // true (localhost) +net_utils.is_local_ipv4('169.254.0.0') // true (link local) +net_utils.is_local_ipv4('226.0.0.1') // false ``` ### is_private_ipv4 ```js // Is IPv4 address in RFC 1918 reserved private address space? -net_utils.is_private_ipv4('10.0.0.0'); // true -net_utils.is_private_ipv4('192.168.0.0'); // true -net_utils.is_private_ipv4('172.16.0.0'); // true +net_utils.is_private_ipv4('10.0.0.0') // true +net_utils.is_private_ipv4('192.168.0.0') // true +net_utils.is_private_ipv4('172.16.0.0') // true ``` ### is_local_ipv6 ```js // Is IPv6 addr on local network? -net_utils.is_local_ipv6('::1'); // true (localhost) -net_utils.is_local_ipv6('fe80::') // true (link local) -net_utils.is_local_ipv6('fc00::') // true (unique local) -net_utils.is_local_ipv6('fd00::') // true (unique local) +net_utils.is_local_ipv6('::1') // true (localhost) +net_utils.is_local_ipv6('fe80::') // true (link local) +net_utils.is_local_ipv6('fc00::') // true (unique local) +net_utils.is_local_ipv6('fd00::') // true (unique local) ``` ### is_private_ip @@ -87,9 +87,9 @@ Checks to see if an IP is bound locally or an IPv4 or IPv6 localhost address. ```js // searches for 'ip' as a hash key in the list object or array // ip can be a host, an IP, or an IPv4 or IPv6 range -net_utils.ip_in_list(object, ip); -net_utils.ip_in_list(array, ip); -net_utils.ip_in_list(tls.no_tls_hosts, '127.0.0.5'); +net_utils.ip_in_list(object, ip) +net_utils.ip_in_list(array, ip) +net_utils.ip_in_list(tls.no_tls_hosts, '127.0.0.5') ``` ### get_ips_by_host @@ -98,13 +98,12 @@ Returns an array of all the IPv4 and IPv6 addresses of the provided hostname. ```js try { - const ips = await net_utils.get_ips_by_host(domain) - for (const ip of ips) { - // do something with the IPs - } -} -catch (err) { - // handle any errors + const ips = await net_utils.get_ips_by_host(domain) + for (const ip of ips) { + // do something with the IPs + } +} catch (err) { + // handle any errors } ``` @@ -112,13 +111,12 @@ catch (err) { ```js try { - const mxList = await net_utils.get_mx(domain) - for (const mx of mxList) { - // do something with each mx - } -} -catch (err) { - // handle any errors + const mxList = await net_utils.get_mx(domain) + for (const mx of mxList) { + // do something with each mx + } +} catch (err) { + // handle any errors } ``` diff --git a/index.js b/index.js index 739b230..77955e7 100644 --- a/index.js +++ b/index.js @@ -1,168 +1,167 @@ -'use strict'; +'use strict' // node.js built-ins -const {Resolver} = require('dns').promises; -const dns = new Resolver({timeout: 25000, tries: 1}); -const net = require('net'); -const os = require('os'); -const punycode = require('punycode/') +const { Resolver } = require('dns').promises +const dns = new Resolver({ timeout: 25000, tries: 1 }) +const net = require('net') +const os = require('os') +const punycode = require('punycode.js') // npm modules -const ipaddr = require('ipaddr.js'); -const sprintf = require('sprintf-js').sprintf; -const tlds = require('haraka-tld'); +const ipaddr = require('ipaddr.js') +const sprintf = require('sprintf-js').sprintf +const tlds = require('haraka-tld') -const locallyBoundIPs = []; +const locallyBoundIPs = [] // export config, so config base path can be overloaded by tests -exports.config = require('haraka-config'); +exports.config = require('haraka-config') exports.long_to_ip = function (n) { - let d = n%256; - for (let i=3; i>0; i--) { - n = Math.floor(n/256); - d = `${n%256}.${d}`; + let d = n % 256 + for (let i = 3; i > 0; i--) { + n = Math.floor(n / 256) + d = `${n % 256}.${d}` } - return d; + return d } exports.dec_to_hex = function (d) { - return d.toString(16); + return d.toString(16) } exports.hex_to_dec = function (h) { - return parseInt(h, 16); + return parseInt(h, 16) } exports.ip_to_long = function (ip) { - if (!net.isIPv4(ip)) { return false; } + if (!net.isIPv4(ip)) return false - const d = ip.split('.'); - return ((((((+d[0])*256)+(+d[1]))*256)+(+d[2]))*256)+(+d[3]); + const d = ip.split('.') + return ((+d[0] * 256 + +d[1]) * 256 + +d[2]) * 256 + +d[3] } exports.octets_in_string = function (str, oct1, oct2) { - let oct1_idx; - let oct2_idx; + let oct1_idx + let oct2_idx // test the largest of the two octets first if (oct2.length >= oct1.length) { - oct2_idx = str.lastIndexOf(oct2); - if (oct2_idx === -1) return false; + oct2_idx = str.lastIndexOf(oct2) + if (oct2_idx === -1) return false - oct1_idx = (str.substring(0, oct2_idx) + - str.substring(oct2_idx + oct2.length)).lastIndexOf(oct1); - if (oct1_idx === -1) return false; + oct1_idx = ( + str.substring(0, oct2_idx) + str.substring(oct2_idx + oct2.length) + ).lastIndexOf(oct1) + if (oct1_idx === -1) return false - return true; // both were found + return true // both were found } - oct1_idx = str.indexOf(oct1); - if (oct1_idx === -1) return false; + oct1_idx = str.indexOf(oct1) + if (oct1_idx === -1) return false - oct2_idx = (str.substring(0, oct1_idx) + - str.substring(oct1_idx + oct1.length)).lastIndexOf(oct2); - if (oct2_idx === -1) return false; + oct2_idx = ( + str.substring(0, oct1_idx) + str.substring(oct1_idx + oct1.length) + ).lastIndexOf(oct2) + if (oct2_idx === -1) return false - return true; + return true } exports.is_ip_in_str = function (ip, str) { - if (!str) return false; - if (!ip) return false; - if (!net.isIPv4(ip)) return false; // IPv4 only, for now + if (!str) return false + if (!ip) return false + if (!net.isIPv4(ip)) return false // IPv4 only, for now - const host_part = (tlds.split_hostname(str,1))[0].toString(); - const octets = ip.split('.'); + const host_part = tlds.split_hostname(str, 1)[0].toString() + const octets = ip.split('.') // See if the 3rd and 4th octets appear in the string if (this.octets_in_string(host_part, octets[2], octets[3])) { - return true; + return true } // then the 1st and 2nd octets if (this.octets_in_string(host_part, octets[0], octets[1])) { - return true; + return true } // Whole IP in hex - let host_part_copy = host_part; - const ip_hex = this.dec_to_hex(this.ip_to_long(ip)); - for (let i=0; i<4; i++) { - const part = host_part_copy.indexOf(ip_hex.substring(i*2, (i*2)+2)); - if (part === -1) break; - if (i === 3) return true; - host_part_copy = host_part_copy.substring(0, part) + - host_part_copy.substring(part+2); + let host_part_copy = host_part + const ip_hex = this.dec_to_hex(this.ip_to_long(ip)) + for (let i = 0; i < 4; i++) { + const part = host_part_copy.indexOf(ip_hex.substring(i * 2, i * 2 + 2)) + if (part === -1) break + if (i === 3) return true + host_part_copy = + host_part_copy.substring(0, part) + host_part_copy.substring(part + 2) } - return false; + return false } const re_ipv4 = { loopback: /^127\./, link_local: /^169\.254\./, - private10: /^10\./, // 10/8 - private192: /^192\.168\./, // 192.168/16 + private10: /^10\./, // 10/8 + private192: /^192\.168\./, // 192.168/16 // 172.16/16 .. 172.31/16 - private172: /^172\.(1[6-9]|2[0-9]|3[01])\./, // 172.16/12 + private172: /^172\.(1[6-9]|2[0-9]|3[01])\./, // 172.16/12 // RFC 5735 - testnet1: /^192\.0\.2\./, // 192.0.2.0/24 + testnet1: /^192\.0\.2\./, // 192.0.2.0/24 testnet2: /^198\.51\.100\./, // 198.51.100.0/24 - testnet3: /^203\.0\.113\./, // 203.0.113.0/24 + testnet3: /^203\.0\.113\./, // 203.0.113.0/24 } exports.is_private_ipv4 = function (ip) { - // RFC 1918, reserved as "private" IP space - if (re_ipv4.private10.test(ip)) return true; - if (re_ipv4.private192.test(ip)) return true; - if (re_ipv4.private172.test(ip)) return true; + if (re_ipv4.private10.test(ip)) return true + if (re_ipv4.private192.test(ip)) return true + if (re_ipv4.private172.test(ip)) return true - if (re_ipv4.testnet1.test(ip)) return true; - if (re_ipv4.testnet2.test(ip)) return true; - if (re_ipv4.testnet3.test(ip)) return true; + if (re_ipv4.testnet1.test(ip)) return true + if (re_ipv4.testnet2.test(ip)) return true + if (re_ipv4.testnet3.test(ip)) return true - return false; + return false } exports.on_local_interface = function (ip) { - if (locallyBoundIPs.length === 0) { - const ifList = os.networkInterfaces(); + const ifList = os.networkInterfaces() for (const ifName of Object.keys(ifList)) { for (const addr of ifList[ifName]) { - locallyBoundIPs.push(addr.address); + locallyBoundIPs.push(addr.address) } } } - return locallyBoundIPs.includes(ip); + return locallyBoundIPs.includes(ip) } exports.is_local_host = async function (dst_host) { - // Is the destination hostname/IP delivered to a hostname or IP // that's local to _this_ mail server? - const local_ips = []; - const dest_ips = []; + const local_ips = [] + const dest_ips = [] try { const public_ip = await this.get_public_ip() if (public_ip) local_ips.push(public_ip) const local_hostname = this.get_primary_host_name() - local_ips.push(...await this.get_ips_by_host(local_hostname)); + local_ips.push(...(await this.get_ips_by_host(local_hostname))) - if (net.isIP(dst_host)) { // an IP address + if (net.isIP(dst_host)) { + // an IP address dest_ips.push(dst_host) - } - else { // a hostname + } else { + // a hostname if (dst_host === local_hostname) return true - dest_ips.push(...await this.get_ips_by_host(dst_host)); + dest_ips.push(...(await this.get_ips_by_host(dst_host))) } - } - catch (e) { + } catch (e) { // console.error(e) return false } @@ -175,119 +174,117 @@ exports.is_local_host = async function (dst_host) { } exports.is_local_ip = function (ip) { + if (this.on_local_interface(ip)) return true - if (this.on_local_interface(ip)) return true; - - if (net.isIPv4(ip)) return this.is_local_ipv4(ip); - if (net.isIPv6(ip)) return this.is_local_ipv6(ip); + if (net.isIPv4(ip)) return this.is_local_ipv4(ip) + if (net.isIPv6(ip)) return this.is_local_ipv6(ip) // console.error(`invalid IP address: ${ip}`); - return false; + return false } exports.is_local_ipv4 = function (ip) { - if ('0.0.0.0' === ip) return true; // RFC 5735 + if ('0.0.0.0' === ip) return true // RFC 5735 // 127/8 (loopback) # RFC 1122 - if (re_ipv4.loopback.test(ip)) return true; + if (re_ipv4.loopback.test(ip)) return true // link local: 169.254/16 RFC 3927 - if (re_ipv4.link_local.test(ip)) return true; + if (re_ipv4.link_local.test(ip)) return true - return false; + return false } const re_ipv6 = { - loopback: /^(0{1,4}:){7}0{0,3}1$/, - link_local: /^fe80::/i, + loopback: /^(0{1,4}:){7}0{0,3}1$/, + link_local: /^fe80::/i, unique_local: /^f(c|d)[a-f0-9]{2}:/i, } exports.is_local_ipv6 = function (ip) { - if (ip === '::') return true; // RFC 5735 - if (ip === '::1') return true; // RFC 4291 + if (ip === '::') return true // RFC 5735 + if (ip === '::1') return true // RFC 4291 // 2 more IPv6 notations for ::1 // 0:0:0:0:0:0:0:1 or 0000:0000:0000:0000:0000:0000:0000:0001 - if (re_ipv6.loopback.test(ip)) return true; + if (re_ipv6.loopback.test(ip)) return true // link local: fe80::/10, RFC 4862 - if (re_ipv6.link_local.test(ip)) return true; + if (re_ipv6.link_local.test(ip)) return true // unique local (fc00::/7) -> fc00: - fd00: - if (re_ipv6.unique_local.test(ip)) return true; + if (re_ipv6.unique_local.test(ip)) return true - return false; + return false } exports.is_private_ip = function (ip) { - if (net.isIPv4(ip)) return this.is_local_ipv4(ip) || this.is_private_ipv4(ip); - if (net.isIPv6(ip)) return this.is_local_ipv6(ip); - return false; + if (net.isIPv4(ip)) return this.is_local_ipv4(ip) || this.is_private_ipv4(ip) + if (net.isIPv6(ip)) return this.is_local_ipv6(ip) + return false } // backwards compatibility for non-public modules. Sunset: v3.0 -exports.is_rfc1918 = exports.is_private_ip; +exports.is_rfc1918 = exports.is_private_ip exports.is_ip_literal = function (host) { - return exports.get_ipany_re('^\\[(IPv6:)?','\\]$','').test(host) ? true : false; + return exports.get_ipany_re('^\\[(IPv6:)?', '\\]$', '').test(host) + ? true + : false } exports.is_ipv4_literal = function (host) { - return /^\[(\d{1,3}\.){3}\d{1,3}\]$/.test(host) ? true : false; + return /^\[(\d{1,3}\.){3}\d{1,3}\]$/.test(host) ? true : false } exports.same_ipv4_network = function (ip, ipList) { if (!ipList || !ipList.length) { - console.error('same_ipv4_network, no ip list!'); - return false; + console.error('same_ipv4_network, no ip list!') + return false } if (!net.isIPv4(ip)) { - console.error('same_ipv4_network, IP is not IPv4!'); - return false; + console.error('same_ipv4_network, IP is not IPv4!') + return false } - const first3 = ip.split('.').slice(0,3).join('.'); + const first3 = ip.split('.').slice(0, 3).join('.') - for (let i=0; i < ipList.length; i++) { + for (let i = 0; i < ipList.length; i++) { if (!net.isIPv4(ipList[i])) { - console.error('same_ipv4_network, IP in list is not IPv4!'); - continue; + console.error('same_ipv4_network, IP in list is not IPv4!') + continue } - if (first3 === ipList[i].split('.').slice(0,3).join('.')) - return true; + if (first3 === ipList[i].split('.').slice(0, 3).join('.')) return true } - return false; + return false } exports.get_public_ip_async = async function () { - - if (this.public_ip !== undefined) return this.public_ip; // cache + if (this.public_ip !== undefined) return this.public_ip // cache // manual config override, for the cases where we can't figure it out - const smtpIni = exports.config.get('smtp.ini').main; + const smtpIni = exports.config.get('smtp.ini').main if (smtpIni.public_ip) { - this.public_ip = smtpIni.public_ip; - return this.public_ip; + this.public_ip = smtpIni.public_ip + return this.public_ip } // Initialise cache value to null to prevent running // should we hit a timeout or the module isn't installed. - this.public_ip = null; + this.public_ip = null try { - this.stun = require('@msimerson/stun'); - } - catch (e) { - e.install = 'Please install stun: "npm install -g stun"'; - console.error(`${e.msg}\n${e.install}`); + this.stun = require('@msimerson/stun') + } catch (e) { + e.install = 'Please install stun: "npm install -g stun"' + console.error(`${e.msg}\n${e.install}`) return } - const timeout = 10; + const timeout = 10 const timer = setTimeout(() => { return new Error('STUN timeout') - }, timeout * 1000); + }, timeout * 1000) // Connect to STUN Server const res = await this.stun.request(get_stun_server()) @@ -299,45 +296,44 @@ exports.get_public_ip_async = async function () { exports.get_public_ip = async function (cb) { if (!cb) return exports.get_public_ip_async() - const nu = this; - if (nu.public_ip !== undefined) return cb(null, nu.public_ip); // cache + const nu = this + if (nu.public_ip !== undefined) return cb(null, nu.public_ip) // cache // manual config override, for the cases where we can't figure it out - const smtpIni = exports.config.get('smtp.ini').main; + const smtpIni = exports.config.get('smtp.ini').main if (smtpIni.public_ip) { - nu.public_ip = smtpIni.public_ip; - return cb(null, nu.public_ip); + nu.public_ip = smtpIni.public_ip + return cb(null, nu.public_ip) } // Initialise cache value to null to prevent running // should we hit a timeout or the module isn't installed. - nu.public_ip = null; + nu.public_ip = null try { - nu.stun = require('@msimerson/stun'); - } - catch (e) { - e.install = 'Please install stun: "npm install -g stun"'; - console.error(`${e.msg}\n${e.install}`); - return cb(e); + nu.stun = require('@msimerson/stun') + } catch (e) { + e.install = 'Please install stun: "npm install -g stun"' + console.error(`${e.msg}\n${e.install}`) + return cb(e) } - const timeout = 10; + const timeout = 10 const timer = setTimeout(() => { - return cb(new Error('STUN timeout')); - }, timeout * 1000); + return cb(new Error('STUN timeout')) + }, timeout * 1000) // Connect to STUN Server nu.stun.request(get_stun_server(), (error, res) => { - if (timer) clearTimeout(timer); - if (error) return cb(error); + if (timer) clearTimeout(timer) + if (error) return cb(error) nu.public_ip = res.getXorAddress().address - cb(null, nu.public_ip); + cb(null, nu.public_ip) }) } -function get_stun_server () { +function get_stun_server() { // STUN servers by Google const servers = [ 'stun.l.google.com:19302', @@ -345,126 +341,130 @@ function get_stun_server () { 'stun2.l.google.com:19302', 'stun3.l.google.com:19302', 'stun4.l.google.com:19302', - ]; - return servers[Math.floor(Math.random()*servers.length)]; + ] + return servers[Math.floor(Math.random() * servers.length)] } exports.get_ipany_re = function (prefix, suffix, modifier) { - if (prefix === undefined) prefix = ''; - if (suffix === undefined) suffix = ''; - if (modifier === undefined) modifier = 'mg'; + if (prefix === undefined) prefix = '' + if (suffix === undefined) suffix = '' + if (modifier === undefined) modifier = 'mg' /* eslint-disable prefer-template */ return new RegExp( prefix + - `(` + // capture group - `(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))` + // complex ipv4 + ipv6 - `)` + // end capture - `${suffix}`, - modifier - ); + `(` + // capture group + `(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))` + // complex ipv4 + ipv6 + `)` + // end capture + `${suffix}`, + modifier, + ) } exports.get_ips_by_host = function (hostname, done) { const ips = new Set() const errors = [] - const promises = [] - async function resolveAny (ver) { - try { - const addrs = await dns[`resolve${ver}`](hostname) - for (const a of addrs) { - ips.add(a); - } - return addrs - } - catch (err) { - errors.push(err); - } - } + return Promise.allSettled([ + dns.resolve6(hostname), + dns.resolve4(hostname), + ]).then((res) => { + res.filter((a) => a.status === 'rejected').map((a) => errors.push(a.reason)) - promises.push(resolveAny('4')) - promises.push(resolveAny('6')) + res + .filter((a) => a.status === 'fulfilled') + .map((a) => a.value.map((ip) => ips.add(ip))) - if (done) { // legacy callback API - Promise.all(promises).then((r) => { done(errors, Array.from(ips)) }) - } - else { // promise API - // if (process.env.DEBUG && errors.length) console.error(errors) - return Promise.all(promises).then(r => { return Array.from(ips) }) - } + if (done) done(errors, Array.from(ips)) + return Array.from(ips) + }) } exports.ipv6_reverse = function (ipv6) { - ipv6 = ipaddr.parse(ipv6); - return ipv6.toNormalizedString() + ipv6 = ipaddr.parse(ipv6) + return ipv6 + .toNormalizedString() .split(':') .map(function (n) { - return sprintf('%04x', parseInt(n, 16)); + return sprintf('%04x', parseInt(n, 16)) }) .join('') .split('') .reverse() - .join('.'); + .join('.') } exports.ipv6_bogus = function (ipv6) { try { - const ipCheck = ipaddr.parse(ipv6); - if (ipCheck.range() !== 'unicast') return true; - return false; - } - catch (e) { + const ipCheck = ipaddr.parse(ipv6) + if (ipCheck.range() !== 'unicast') return true + return false + } catch (e) { // If we get an error from parsing, return true for bogus. - console.error(e); - return true; + console.error(e) + return true } } exports.ip_in_list = function (list, ip) { - if (list === undefined) return false; + if (list === undefined) return false - const isHostname = !net.isIP(ip); - const isArray = Array.isArray(list); + const isHostname = !net.isIP(ip) + const isArray = Array.isArray(list) // Quick lookup if (!isArray) { - if (ip in list) return true; // domain or literal IP - if (isHostname) return false; // skip CIDR match + if (ip in list) return true // domain or literal IP + if (isHostname) return false // skip CIDR match } // Iterate: arrays and CIDR matches for (let item in list) { if (isArray) { - item = list[item]; // item is index - if (item === ip) return true; // exact match + item = list[item] // item is index + if (item === ip) return true // exact match } - if (isHostname) continue; // skip CIDR match + if (isHostname) continue // skip CIDR match - const cidr = item.split('/'); - const c_net = cidr[0]; + const cidr = item.split('/') + const c_net = cidr[0] - if (!net.isIP(c_net)) continue; // bad config entry - if (net.isIPv4(ip) && net.isIPv6(c_net)) continue; - if (net.isIPv6(ip) && net.isIPv4(c_net)) continue; + if (!net.isIP(c_net)) continue // bad config entry + if (net.isIPv4(ip) && net.isIPv6(c_net)) continue + if (net.isIPv6(ip) && net.isIPv4(c_net)) continue - const c_mask = parseInt(cidr[1], 10) || (net.isIPv6(c_net) ? 128 : 32); + const c_mask = parseInt(cidr[1], 10) || (net.isIPv6(c_net) ? 128 : 32) if (ipaddr.parse(ip).match(ipaddr.parse(c_net), c_mask)) { - return true; + return true } } - return false; + return false } exports.get_primary_host_name = function () { - return exports.config.get('me') || os.hostname(); + return exports.config.get('me') || os.hostname() } -exports.get_mx = async function get_mx (raw_domain, cb) { - let domain = raw_domain; - const mxs = []; +function normalizeDomain(raw_domain) { + let domain = raw_domain + + if (/@/.test(domain)) { + domain = domain.split('@').pop() + // console.log(`\treduced ${raw_domain} to ${domain}.`) + } + + if (/^xn--/.test(domain)) { + // is punycode IDN with ACE, ASCII Compatible Encoding + } else if (domain !== punycode.toASCII(domain)) { + domain = punycode.toASCII(domain) + console.log(`\tACE encoded '${raw_domain}' to '${domain}'`) + } + + return domain +} +function fatal_mx_err(err) { // Possible DNS errors // NODATA // FORMERR @@ -479,36 +479,94 @@ exports.get_mx = async function get_mx (raw_domain, cb) { // EREFUSED // SERVFAIL - if ( /@/.test(domain) ) { - domain = domain.split('@').pop(); - // console.log(`\treduced ${raw_domain} to ${domain}.`) + switch (err.code) { + case 'ENODATA': + case 'ENOTFOUND': + // likely a hostname with no MX record, drop through + return false + default: + return err } +} - if ( /^xn--/.test(domain) ) { - // is punycode IDN with ACE, ASCII Compatible Encoding - } - else if (domain !== punycode.toASCII(domain)) { - domain = punycode.toASCII(domain); - console.log(`\tACE encoded '${raw_domain}' to '${domain}'`) - } +exports.get_mx = async (raw_domain, cb) => { + const domain = normalizeDomain(raw_domain) - // wrap_mx returns our object with "priority" and "exchange" keys - const wrap_mx = a => a; - let err = null + try { + const exchanges = await dns.resolveMx(domain) + if (exchanges && exchanges.length) { + exchanges.map((e) => (e.from_dns = domain)) + if (cb) return cb(null, exchanges) + return exchanges + } + } catch (err) { + // console.error(err.message) + if (fatal_mx_err(err)) { + if (cb) return cb(err, []) + throw err + } + } + // no MX or non-fatal DNS failure try { - const addresses = await dns.resolveMx(domain) - if (addresses?.length) { - for (const addr of addresses) { - mxs.push(wrap_mx(addr)); - } + const exchanges = await this.get_implicit_mx(domain) + if (cb) return cb(null, exchanges) + return exchanges + } catch (err) { + if (fatal_mx_err(err)) { + if (cb) return cb(err, []) + throw err } } - catch (e) { - // console.error(e.message) - err = e +} + +exports.get_implicit_mx = async (domain) => { + // console.log(`No MX for ${domain}, trying AAAA & A records`) + + const promises = [dns.resolve6(domain), dns.resolve4(domain)] + const r = await Promise.allSettled(promises) + + return r + .filter((a) => a.status === 'fulfilled') + .flatMap((a) => + a.value.map((ip) => ({ priority: 0, exchange: ip, from_dns: domain })), + ) +} + +exports.resolve_mx_hosts = async (mxes) => { + // for the given list of MX exchanges, resolve the hostnames to IPs + const promises = [] + + for (const mx of mxes) { + if (!mx.exchange) { + promises.push(mx) + continue + } + + if (net.isIP(mx.exchange)) { + promises.push(mx) // already resolved + continue + } + + // resolve AAAA and A since mx.exchange is a hostname + promises.push( + dns + .resolve6(mx.exchange) + .then((ips) => + ips.map((ip) => ({ ...mx, exchange: ip, from_dns: mx.exchange })), + ), + ) + + promises.push( + dns + .resolve4(mx.exchange) + .then((ips) => + ips.map((ip) => ({ ...mx, exchange: ip, from_dns: mx.exchange })), + ), + ) } - if (cb) return cb(err, mxs) - return mxs + const settled = await Promise.allSettled(promises) + + return settled.filter((s) => s.status === 'fulfilled').flatMap((s) => s.value) } diff --git a/package.json b/package.json index e5e06ee..dd51bb9 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,20 @@ { "name": "haraka-net-utils", - "version": "1.5.4", + "version": "1.6.0", "description": "haraka network utilities", "main": "index.js", + "files": [ + "CHANGELOG.md" + ], "scripts": { - "test": "npx mocha", - "lint": "npx eslint *.js test", - "lintfix": "npx eslint --fix *.js test", - "versions": "npx dependency-version-checker check" + "format": "npm run prettier:fix && npm run lint:fix", + "lint": "npx eslint@^8 *.js test", + "lint:fix": "npx eslint@^8 *.js test --fix", + "prettier": "npx prettier . --check", + "prettier:fix": "npx prettier . --write --log-level=warn", + "test": "npx mocha@10", + "versions": "npx dependency-version-checker check", + "versions:fix": "npx dependency-version-checker update && npm run prettier:fix" }, "repository": { "type": "git", @@ -29,15 +36,13 @@ }, "homepage": "https://github.com/haraka/haraka-net-utils#readme", "devDependencies": { - "eslint": "^8.57.0", - "mocha": "^10.4.0", - "eslint-plugin-haraka": "1.0.15" + "@haraka/eslint-config": "^1.1.3" }, "dependencies": { "haraka-config": "^1.1.0", - "haraka-tld": "^1.2.0", + "haraka-tld": "^1.2.1", "ipaddr.js": "^2.1.0", - "punycode": "^2.3.1", + "punycode.js": "^2.3.1", "openssl-wrapper": "^0.3.4", "sprintf-js": "^1.1.3" }, diff --git a/test/net_utils.js b/test/net_utils.js index 71f28af..a547a83 100644 --- a/test/net_utils.js +++ b/test/net_utils.js @@ -1,1053 +1,1096 @@ - const assert = require('assert') -const net = require('net') -const os = require('os') -const path = require('path') +const net = require('net') +const os = require('os') +const path = require('path') -require('haraka-config').watch_files = false; -const net_utils = require('../index'); +require('haraka-config').watch_files = false +const net_utils = require('../index') -function _check (done, ip, host, res) { - assert.equal(net_utils.is_ip_in_str(ip, host), res); - done(); +function _check(done, ip, host, res) { + assert.equal(net_utils.is_ip_in_str(ip, host), res) + done() } describe('long_to_ip', function () { it('185999660', function (done) { - assert.equal(net_utils.long_to_ip(185999660), '11.22.33.44'); - done(); + assert.equal(net_utils.long_to_ip(185999660), '11.22.33.44') + done() }) }) describe('static_rdns', function () { it('74.125.82.182', function (done) { - _check(done, '74.125.82.182', 'mail-we0-f182.google.com', false); + _check(done, '74.125.82.182', 'mail-we0-f182.google.com', false) }) it('74.125.82.53', function (done) { - _check(done, '74.125.82.53', 'mail-ww0-f53.google.com', false); + _check(done, '74.125.82.53', 'mail-ww0-f53.google.com', false) }) }) describe('dynamic_rdns', function () { - it('109.168.232.131', function (done) { - _check(done, '109.168.232.131', 'host-109-168-232-131.stv.ru', true); + _check(done, '109.168.232.131', 'host-109-168-232-131.stv.ru', true) }) it('62.198.236.129', function (done) { - _check(done, '62.198.236.129', '0x3ec6ec81.inet.dsl.telianet.dk', true); + _check(done, '62.198.236.129', '0x3ec6ec81.inet.dsl.telianet.dk', true) }) it('123.58.178.17', function (done) { - _check(done, '123.58.178.17', 'm17-178.vip.126.com', true); + _check(done, '123.58.178.17', 'm17-178.vip.126.com', true) }) it('100.42.67.92', function (done) { - _check(done, '100.42.67.92', '92-67-42-100-dedicated.multacom.com', - true); + _check(done, '100.42.67.92', '92-67-42-100-dedicated.multacom.com', true) }) it('101.0.57.5', function (done) { - _check(done, '101.0.57.5', 'static-bpipl-101.0.57-5.com', true); + _check(done, '101.0.57.5', 'static-bpipl-101.0.57-5.com', true) }) }) -function _same_ipv4_network (done, addr, addrList, expected) { - assert.equal(expected, net_utils.same_ipv4_network(addr, addrList)); - done(); +function _same_ipv4_network(done, addr, addrList, expected) { + assert.equal(expected, net_utils.same_ipv4_network(addr, addrList)) + done() } describe('same_ipv4_network', function () { it('199.176.179.3 <-> [199.176.179.4]', function (done) { - _same_ipv4_network(done, '199.176.179.3', ['199.176.179.4'], true); + _same_ipv4_network(done, '199.176.179.3', ['199.176.179.4'], true) }) it('199.176.179.3 <-> [199.177.179.4', function (done) { - _same_ipv4_network(done, '199.176.179.3', ['199.177.179.4'], false); + _same_ipv4_network(done, '199.176.179.3', ['199.177.179.4'], false) }) it('199.176.179 <-> [199.176.179.4] (missing octet)', function (done) { - _same_ipv4_network(done, '199.176.179', ['199.176.179.4'], false); + _same_ipv4_network(done, '199.176.179', ['199.176.179.4'], false) }) it('199.176.179.3.5 <-> [199.176.179.4] (extra octet)', function (done) { - _same_ipv4_network(done, '199.176.179.3.5', ['199.176.179.4'], false); + _same_ipv4_network(done, '199.176.179.3.5', ['199.176.179.4'], false) }) }) describe('is_ipv4_literal', function () { it('3 ways', function (done) { - assert.equal(true, net_utils.is_ipv4_literal('[127.0.0.1]')); - assert.equal(false, net_utils.is_ipv4_literal('127.0.0.1')); - assert.equal(false, net_utils.is_ipv4_literal('test.host')); - done(); + assert.equal(true, net_utils.is_ipv4_literal('[127.0.0.1]')) + assert.equal(false, net_utils.is_ipv4_literal('127.0.0.1')) + assert.equal(false, net_utils.is_ipv4_literal('test.host')) + done() }) }) -async function _is_local_host (done, host, expected) { - const is_local_host = await net_utils.is_local_host(host); - assert.strictEqual(expected, is_local_host); - done(); +async function _is_local_host(done, host, expected) { + const is_local_host = await net_utils.is_local_host(host) + assert.strictEqual(expected, is_local_host) + done() } -function _is_private_ip (done, ip, expected) { - assert.equal(expected, net_utils.is_private_ip(ip)); - done(); +function _is_private_ip(done, ip, expected) { + assert.equal(expected, net_utils.is_private_ip(ip)) + done() } -function _is_local_ip (done, ip, expected) { - assert.equal(expected, net_utils.is_local_ip(ip)); - done(); +function _is_local_ip(done, ip, expected) { + assert.equal(expected, net_utils.is_local_ip(ip)) + done() } describe('is_local_host', function () { it('127.0.0.1', function (done) { - _is_local_host(done, '127.0.0.1', true); + _is_local_host(done, '127.0.0.1', true) }) it('0.0.0.0', function (done) { - _is_local_host(done, '0.0.0.0', true); + _is_local_host(done, '0.0.0.0', true) }) it('::1', function (done) { - _is_local_host(done, '::1', true); + _is_local_host(done, '::1', true) }) it('self hostname', function (done) { if (/^win/.test(process.platform)) return done() - const hostname = require('../index').get_primary_host_name(); - _is_local_host(done, hostname, true); + const hostname = require('../index').get_primary_host_name() + _is_local_host(done, hostname, true) }) it('self ip', function (done) { - require('../index').get_public_ip().then(ip => { - _is_local_host(done, ip, true); - }); + require('../index') + .get_public_ip() + .then((ip) => { + _is_local_host(done, ip, true) + }) }) it('google.com', function (done) { - _is_local_host(done, 'google.com', false); + _is_local_host(done, 'google.com', false) }) it('8.8.8.8', function (done) { - _is_local_host(done, '8.8.8.8', false); + _is_local_host(done, '8.8.8.8', false) }) it('invalid host string', async function () { const r = await net_utils.is_local_host('invalid host string') - assert.ok(!r); + assert.ok(!r) }) }) describe('is_local_ip', function () { it('127.0.0.1', function (done) { - _is_local_ip(done, '127.0.0.1', true); + _is_local_ip(done, '127.0.0.1', true) }) it('::1', function (done) { - _is_local_ip(done, '::1', true); + _is_local_ip(done, '::1', true) }) it('0:0:0:0:0:0:0:1', function (done) { - _is_local_ip(done, '0:0:0:0:0:0:0:1', true); + _is_local_ip(done, '0:0:0:0:0:0:0:1', true) }) it('0000:0000:0000:0000:0000:0000:0000:0001', function (done) { - _is_local_ip(done, '0000:0000:0000:0000:0000:0000:0000:0001', true); + _is_local_ip(done, '0000:0000:0000:0000:0000:0000:0000:0001', true) }) it('123.123.123.123 (!)', function (done) { - _is_local_ip(done, '123.123.123.123', false); + _is_local_ip(done, '123.123.123.123', false) }) it('dead::beef (!)', function (done) { - _is_local_ip(done, 'dead::beef', false); + _is_local_ip(done, 'dead::beef', false) }) it('192.168.1 (missing octet)', function (done) { - _is_local_ip(done, '192.168.1', false); + _is_local_ip(done, '192.168.1', false) }) it('239.0.0.1 (multicast; not currently considered rfc1918)', function (done) { - _is_local_ip(done, '239.0.0.1', false); + _is_local_ip(done, '239.0.0.1', false) }) it('0.0.0.0', function (done) { - _is_local_ip(done, '0.0.0.0', true); + _is_local_ip(done, '0.0.0.0', true) }) it('::', function (done) { - _is_local_ip(done, '::', true); + _is_local_ip(done, '::', true) }) }) describe('is_private_ip', function () { it('127.0.0.1', function (done) { - _is_private_ip(done, '127.0.0.1', true); + _is_private_ip(done, '127.0.0.1', true) }) it('10.255.31.23', function (done) { - _is_private_ip(done, '10.255.31.23', true); + _is_private_ip(done, '10.255.31.23', true) }) it('172.16.255.254', function (done) { - _is_private_ip(done, '172.16.255.254', true); + _is_private_ip(done, '172.16.255.254', true) }) it('192.168.123.123', function (done) { - _is_private_ip(done, '192.168.123.123', true); + _is_private_ip(done, '192.168.123.123', true) }) it('169.254.23.54 (APIPA)', function (done) { - _is_private_ip(done, '169.254.23.54', true); + _is_private_ip(done, '169.254.23.54', true) }) it('::1', function (done) { - _is_private_ip(done, '::1', true); + _is_private_ip(done, '::1', true) }) it('0:0:0:0:0:0:0:1', function (done) { - _is_private_ip(done, '0:0:0:0:0:0:0:1', true); + _is_private_ip(done, '0:0:0:0:0:0:0:1', true) }) it('0000:0000:0000:0000:0000:0000:0000:0001', function (done) { - _is_private_ip(done, '0000:0000:0000:0000:0000:0000:0000:0001', true); + _is_private_ip(done, '0000:0000:0000:0000:0000:0000:0000:0001', true) }) it('123.123.123.123', function (done) { - _is_private_ip(done, '123.123.123.123', false); + _is_private_ip(done, '123.123.123.123', false) }) it('dead::beef', function (done) { - _is_private_ip(done, 'dead::beef', false); + _is_private_ip(done, 'dead::beef', false) }) it('192.168.1 (missing octet)', function (done) { - _is_private_ip(done, '192.168.1', false); + _is_private_ip(done, '192.168.1', false) }) it('239.0.0.1 (multicast; not currently considered rfc1918)', function (done) { - _is_private_ip(done, '239.0.0.1', false); + _is_private_ip(done, '239.0.0.1', false) }) it('192.0.2.1 TEST-NET-1', function (done) { - _is_private_ip(done, '192.0.2.1', true); + _is_private_ip(done, '192.0.2.1', true) }) it('198.51.100.0 TEST-NET-2', function (done) { - _is_private_ip(done, '198.51.100.0', true); + _is_private_ip(done, '198.51.100.0', true) }) it('203.0.113.0 TEST-NET-3', function (done) { - _is_private_ip(done, '203.0.113.0', true); + _is_private_ip(done, '203.0.113.0', true) }) }) describe('get_public_ip', function () { - beforeEach(function (done) { - this.net_utils = require('../index'); - this.net_utils.config = this.net_utils.config.module_config(path.resolve('test')); - done(); + this.net_utils = require('../index') + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('test'), + ) + done() }) - function has_stun () { + function has_stun() { try { - require('stun'); - } - catch (e) { - return false; + require('stun') + } catch (e) { + return false } - return true; + return true } it('cached', function (done) { - this.net_utils.public_ip='1.1.1.1'; + this.net_utils.public_ip = '1.1.1.1' const cb = function (err, ip) { - assert.equal(null, err); - assert.equal('1.1.1.1', ip); - done(); - }; - this.net_utils.get_public_ip(cb); + assert.equal(null, err) + assert.equal('1.1.1.1', ip) + done() + } + this.net_utils.get_public_ip(cb) }) it('normal', function (done) { - this.net_utils.public_ip=undefined; + this.net_utils.public_ip = undefined const cb = function (err, ip) { // console.log(`ip: ${ip}`); // console.log(`err: ${err}`); if (has_stun()) { if (err) { - console.log(err); + console.log(err) + } else { + console.log(`stun success: ${ip}`) + assert.equal(null, err) + assert.ok(ip, ip) } - else { - console.log(`stun success: ${ip}`); - assert.equal(null, err); - assert.ok(ip, ip); - } - } - else { - console.log("stun skipped"); + } else { + console.log('stun skipped') } - done(); - }; - this.net_utils.get_public_ip(cb); + done() + } + this.net_utils.get_public_ip(cb) }) }) describe('get_public_ip_async', function () { - beforeEach(() => { - this.net_utils = require('../index'); - this.net_utils.config = this.net_utils.config.module_config(path.resolve('test')); + this.net_utils = require('../index') + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('test'), + ) }) - function has_stun () { + function has_stun() { try { - require('stun'); - } - catch (e) { - return false; + require('stun') + } catch (e) { + return false } - return true; + return true } it('cached', async () => { - this.net_utils.public_ip='1.1.1.1'; + this.net_utils.public_ip = '1.1.1.1' const ip = await this.net_utils.get_public_ip() - assert.equal('1.1.1.1', ip); + assert.equal('1.1.1.1', ip) }) it('normal', async () => { - this.net_utils.public_ip=undefined; + this.net_utils.public_ip = undefined if (!has_stun()) { - console.log("stun skipped"); + console.log('stun skipped') return } try { const ip = await this.net_utils.get_public_ip() - console.log(`stun success: ${ip}`); - assert.ok(ip, ip); - } - catch (e) { - console.error(e); + console.log(`stun success: ${ip}`) + assert.ok(ip, ip) + } catch (e) { + console.error(e) } }) }) describe('octets_in_string', function () { it('c-24-18-98-14.hsd1.wa.comcast.net', function (done) { - const str = 'c-24-18-98-14.hsd1.wa.comcast.net'; - assert.equal(net_utils.octets_in_string(str, 98, 14), true ); - assert.equal(net_utils.octets_in_string(str, 24, 18), true ); - assert.equal(net_utils.octets_in_string(str, 2, 7), false ); - done(); + const str = 'c-24-18-98-14.hsd1.wa.comcast.net' + assert.equal(net_utils.octets_in_string(str, 98, 14), true) + assert.equal(net_utils.octets_in_string(str, 24, 18), true) + assert.equal(net_utils.octets_in_string(str, 2, 7), false) + done() }) it('149.213.210.203.in-addr.arpa', function (done) { - const str = '149.213.210.203.in-addr.arpa'; - assert.equal(net_utils.octets_in_string(str, 149, 213), true ); - assert.equal(net_utils.octets_in_string(str, 210, 20), true ); - assert.equal(net_utils.octets_in_string(str, 2, 7), false ); - done(); + const str = '149.213.210.203.in-addr.arpa' + assert.equal(net_utils.octets_in_string(str, 149, 213), true) + assert.equal(net_utils.octets_in_string(str, 210, 20), true) + assert.equal(net_utils.octets_in_string(str, 2, 7), false) + done() }) }) describe('is_ip_literal', function () { it('ipv4 is_ip_literal', function (done) { - assert.equal(net_utils.is_ip_literal('[127.0.0.0]'), true); - assert.equal(net_utils.is_ip_literal('[127.0.0.1]'), true); - assert.equal(net_utils.is_ip_literal('[127.1.0.255]'), true); - assert.equal(net_utils.is_ip_literal('127.0.0.0'), false); - assert.equal(net_utils.is_ip_literal('127.0.0.1'), false); - assert.equal(net_utils.is_ip_literal('127.1.0.255'), false); + assert.equal(net_utils.is_ip_literal('[127.0.0.0]'), true) + assert.equal(net_utils.is_ip_literal('[127.0.0.1]'), true) + assert.equal(net_utils.is_ip_literal('[127.1.0.255]'), true) + assert.equal(net_utils.is_ip_literal('127.0.0.0'), false) + assert.equal(net_utils.is_ip_literal('127.0.0.1'), false) + assert.equal(net_utils.is_ip_literal('127.1.0.255'), false) - done(); + done() }) it('ipv6 is_ip_literal', function (done) { - assert.equal(net_utils.is_ip_literal('[::5555:6666:7777:8888]'), true); - assert.equal(net_utils.is_ip_literal('[1111::4444:5555:6666:7777:8888]'), true); - assert.equal(net_utils.is_ip_literal('[2001:0:1234::C1C0:ABCD:876]'), true); - assert.equal(net_utils.is_ip_literal('[IPv6:2607:fb90:4c28:f9e9:4ca2:2658:db85:f1a]'), true); - assert.equal(net_utils.is_ip_literal('::5555:6666:7777:8888'), false); - assert.equal(net_utils.is_ip_literal('1111::4444:5555:6666:7777:8888'), false); - assert.equal(net_utils.is_ip_literal('2001:0:1234::C1C0:ABCD:876'), false); - - done(); + assert.equal(net_utils.is_ip_literal('[::5555:6666:7777:8888]'), true) + assert.equal( + net_utils.is_ip_literal('[1111::4444:5555:6666:7777:8888]'), + true, + ) + assert.equal(net_utils.is_ip_literal('[2001:0:1234::C1C0:ABCD:876]'), true) + assert.equal( + net_utils.is_ip_literal('[IPv6:2607:fb90:4c28:f9e9:4ca2:2658:db85:f1a]'), + true, + ) + assert.equal(net_utils.is_ip_literal('::5555:6666:7777:8888'), false) + assert.equal( + net_utils.is_ip_literal('1111::4444:5555:6666:7777:8888'), + false, + ) + assert.equal(net_utils.is_ip_literal('2001:0:1234::C1C0:ABCD:876'), false) + + done() }) }) describe('is_local_ipv4', function () { it('127/8', function (done) { - assert.equal(net_utils.is_local_ipv4('127.0.0.0'), true); - assert.equal(net_utils.is_local_ipv4('127.0.0.1'), true); - assert.equal(net_utils.is_local_ipv4('127.1.0.255'), true); + assert.equal(net_utils.is_local_ipv4('127.0.0.0'), true) + assert.equal(net_utils.is_local_ipv4('127.0.0.1'), true) + assert.equal(net_utils.is_local_ipv4('127.1.0.255'), true) - done(); + done() }) it('0/8', function (done) { - assert.equal(net_utils.is_local_ipv4('0.0.0.1'), false); - assert.equal(net_utils.is_local_ipv4('0.255.0.1'), false); - assert.equal(net_utils.is_local_ipv4('1.255.0.1'), false); - assert.equal(net_utils.is_local_ipv4('10.255.0.1'), false); - done(); + assert.equal(net_utils.is_local_ipv4('0.0.0.1'), false) + assert.equal(net_utils.is_local_ipv4('0.255.0.1'), false) + assert.equal(net_utils.is_local_ipv4('1.255.0.1'), false) + assert.equal(net_utils.is_local_ipv4('10.255.0.1'), false) + done() }) }) describe('is_private_ipv4', function () { it('10/8', function (done) { - assert.equal(net_utils.is_private_ipv4('10.0.0.0'), true); - assert.equal(net_utils.is_private_ipv4('10.255.0.0'), true); - assert.equal(net_utils.is_private_ipv4('9.255.0.0'), false); - assert.equal(net_utils.is_private_ipv4('11.255.0.0'), false); - done(); + assert.equal(net_utils.is_private_ipv4('10.0.0.0'), true) + assert.equal(net_utils.is_private_ipv4('10.255.0.0'), true) + assert.equal(net_utils.is_private_ipv4('9.255.0.0'), false) + assert.equal(net_utils.is_private_ipv4('11.255.0.0'), false) + done() }) it('192.168/16', function (done) { - assert.equal(net_utils.is_private_ipv4('192.168.0.0'), true); - assert.equal(net_utils.is_private_ipv4('192.169.0.0'), false); - assert.equal(net_utils.is_private_ipv4('192.167.0.0'), false); - done(); + assert.equal(net_utils.is_private_ipv4('192.168.0.0'), true) + assert.equal(net_utils.is_private_ipv4('192.169.0.0'), false) + assert.equal(net_utils.is_private_ipv4('192.167.0.0'), false) + done() }) it('172.16-31', function (done) { - assert.equal(net_utils.is_private_ipv4('172.16.0.0'), true); - assert.equal(net_utils.is_private_ipv4('172.20.0.0'), true); - assert.equal(net_utils.is_private_ipv4('172.31.0.0'), true); - assert.equal(net_utils.is_private_ipv4('172.15.0.0'), false); - assert.equal(net_utils.is_private_ipv4('172.32.0.0'), false); - done(); + assert.equal(net_utils.is_private_ipv4('172.16.0.0'), true) + assert.equal(net_utils.is_private_ipv4('172.20.0.0'), true) + assert.equal(net_utils.is_private_ipv4('172.31.0.0'), true) + assert.equal(net_utils.is_private_ipv4('172.15.0.0'), false) + assert.equal(net_utils.is_private_ipv4('172.32.0.0'), false) + done() }) }) describe('is_local_ipv6', function () { it('::', function (done) { - assert.equal(net_utils.is_local_ipv6('::'), true); - done(); + assert.equal(net_utils.is_local_ipv6('::'), true) + done() }) it('::1', function (done) { - assert.equal(net_utils.is_local_ipv6('::1'), true); - assert.equal(net_utils.is_local_ipv6('0:0:0:0:0:0:0:1'), true); - assert.equal(net_utils.is_local_ipv6( - '0000:0000:0000:0000:0000:0000:0000:0001'), true); - done(); + assert.equal(net_utils.is_local_ipv6('::1'), true) + assert.equal(net_utils.is_local_ipv6('0:0:0:0:0:0:0:1'), true) + assert.equal( + net_utils.is_local_ipv6('0000:0000:0000:0000:0000:0000:0000:0001'), + true, + ) + done() }) it('fe80::/10', function (done) { - assert.equal(net_utils.is_local_ipv6('fe80::'), true); - assert.equal(net_utils.is_local_ipv6('fe80:'), false); - assert.equal(net_utils.is_local_ipv6('fe8:'), false); - assert.equal(net_utils.is_local_ipv6(':fe80:'), false); - done(); + assert.equal(net_utils.is_local_ipv6('fe80::'), true) + assert.equal(net_utils.is_local_ipv6('fe80:'), false) + assert.equal(net_utils.is_local_ipv6('fe8:'), false) + assert.equal(net_utils.is_local_ipv6(':fe80:'), false) + done() }) it('fc80::/7', function (done) { - assert.equal(net_utils.is_local_ipv6('fc00:'), true); - assert.equal(net_utils.is_local_ipv6('fcff:'), true); + assert.equal(net_utils.is_local_ipv6('fc00:'), true) + assert.equal(net_utils.is_local_ipv6('fcff:'), true) // examples from https://en.wikipedia.org/wiki/Unique_local_address - assert.equal(net_utils.is_local_ipv6('fde4:8dba:82e1::'), true); - assert.equal(net_utils.is_local_ipv6('fde4:8dba:82e1:ffff::'), true); + assert.equal(net_utils.is_local_ipv6('fde4:8dba:82e1::'), true) + assert.equal(net_utils.is_local_ipv6('fde4:8dba:82e1:ffff::'), true) - assert.equal(net_utils.is_local_ipv6('fd00:'), true); - assert.equal(net_utils.is_local_ipv6('fdff:'), true); + assert.equal(net_utils.is_local_ipv6('fd00:'), true) + assert.equal(net_utils.is_local_ipv6('fdff:'), true) - assert.equal(net_utils.is_local_ipv6('fb00:'), false); - assert.equal(net_utils.is_local_ipv6('fe00:'), false); + assert.equal(net_utils.is_local_ipv6('fb00:'), false) + assert.equal(net_utils.is_local_ipv6('fe00:'), false) - assert.equal(net_utils.is_local_ipv6('fe8:'), false); - assert.equal(net_utils.is_local_ipv6(':fe80:'), false); - done(); + assert.equal(net_utils.is_local_ipv6('fe8:'), false) + assert.equal(net_utils.is_local_ipv6(':fe80:'), false) + done() }) }) const ip_fixtures = [ - [false , " 2001:0000:1234:0000:0000:C1C0:ABCD:0876 "], - [false , " 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0"], - [false , " 2001:0000:1234:0000:0000:C1C0:ABCD:0876"], - [false , " 2001:0:1234::C1C0:ABCD:876 "], - [false , " 2001:0:1234::C1C0:ABCD:876"], - [false , ""], - [false , "':10.0.0.1"], - [false , "---"], - [false , "02001:0000:1234:0000:0000:C1C0:ABCD:0876"], - [false , "1.2.3.4:1111:2222:3333:4444::5555"], - [false , "1.2.3.4:1111:2222:3333::5555"], - [false , "1.2.3.4:1111:2222::5555"], - [false , "1.2.3.4:1111::5555"], - [false , "1.2.3.4::"], - [false , "1.2.3.4::5555"], - [false , "1111"], - [false , "11112222:3333:4444:5555:6666:1.2.3.4"], - [false , "11112222:3333:4444:5555:6666:7777:8888"], - [false , "1111:"], - [false , "1111:1.2.3.4"], - [false , "1111:2222"], - [false , "1111:22223333:4444:5555:6666:1.2.3.4"], - [false , "1111:22223333:4444:5555:6666:7777:8888"], - [false , "1111:2222:"], - [false , "1111:2222:1.2.3.4"], - [false , "1111:2222:3333"], - [false , "1111:2222:33334444:5555:6666:1.2.3.4"], - [false , "1111:2222:33334444:5555:6666:7777:8888"], - [false , "1111:2222:3333:"], - [false , "1111:2222:3333:1.2.3.4"], - [false , "1111:2222:3333:4444"], - [false , "1111:2222:3333:44445555:6666:1.2.3.4"], - [false , "1111:2222:3333:44445555:6666:7777:8888"], - [false , "1111:2222:3333:4444:"], - [false , "1111:2222:3333:4444:1.2.3.4"], - [false , "1111:2222:3333:4444:5555"], - [false , "1111:2222:3333:4444:55556666:1.2.3.4"], - [false , "1111:2222:3333:4444:55556666:7777:8888"], - [false , "1111:2222:3333:4444:5555:"], - [false , "1111:2222:3333:4444:5555:1.2.3.4"], - [false , "1111:2222:3333:4444:5555:6666"], - [false , "1111:2222:3333:4444:5555:66661.2.3.4"], - [false , "1111:2222:3333:4444:5555:66667777:8888"], - [false , "1111:2222:3333:4444:5555:6666:"], - [false , "1111:2222:3333:4444:5555:6666:00.00.00.00"], - [false , "1111:2222:3333:4444:5555:6666:000.000.000.000"], - [false , "1111:2222:3333:4444:5555:6666:1.2.3.4.5"], - [false , "1111:2222:3333:4444:5555:6666:255.255.255255"], - [false , "1111:2222:3333:4444:5555:6666:255.255255.255"], - [false , "1111:2222:3333:4444:5555:6666:255255.255.255"], - [false , "1111:2222:3333:4444:5555:6666:256.256.256.256"], - [false , "1111:2222:3333:4444:5555:6666:7777"], - [false , "1111:2222:3333:4444:5555:6666:77778888"], - [false , "1111:2222:3333:4444:5555:6666:7777:"], - [false , "1111:2222:3333:4444:5555:6666:7777:1.2.3.4"], - [false , "1111:2222:3333:4444:5555:6666:7777:8888:"], - [false , "1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4"], - [false , "1111:2222:3333:4444:5555:6666:7777:8888:9999"], - [false , "1111:2222:3333:4444:5555:6666:7777:8888::"], - [false , "1111:2222:3333:4444:5555:6666:7777:::"], - [false , "1111:2222:3333:4444:5555:6666::1.2.3.4"], - [false , "1111:2222:3333:4444:5555:6666::8888:"], - [false , "1111:2222:3333:4444:5555:6666:::"], - [false , "1111:2222:3333:4444:5555:6666:::8888"], - [false , "1111:2222:3333:4444:5555::7777:8888:"], - [false , "1111:2222:3333:4444:5555::7777::"], - [false , "1111:2222:3333:4444:5555::8888:"], - [false , "1111:2222:3333:4444:5555:::"], - [false , "1111:2222:3333:4444:5555:::1.2.3.4"], - [false , "1111:2222:3333:4444:5555:::7777:8888"], - [false , "1111:2222:3333:4444::5555:"], - [false , "1111:2222:3333:4444::6666:7777:8888:"], - [false , "1111:2222:3333:4444::6666:7777::"], - [false , "1111:2222:3333:4444::6666::8888"], - [false , "1111:2222:3333:4444::7777:8888:"], - [false , "1111:2222:3333:4444::8888:"], - [false , "1111:2222:3333:4444:::"], - [false , "1111:2222:3333:4444:::6666:1.2.3.4"], - [false , "1111:2222:3333:4444:::6666:7777:8888"], - [false , "1111:2222:3333::5555:"], - [false , "1111:2222:3333::5555:6666:7777:8888:"], - [false , "1111:2222:3333::5555:6666:7777::"], - [false , "1111:2222:3333::5555:6666::8888"], - [false , "1111:2222:3333::5555::1.2.3.4"], - [false , "1111:2222:3333::5555::7777:8888"], - [false , "1111:2222:3333::6666:7777:8888:"], - [false , "1111:2222:3333::7777:8888:"], - [false , "1111:2222:3333::8888:"], - [false , "1111:2222:3333:::"], - [false , "1111:2222:3333:::5555:6666:1.2.3.4"], - [false , "1111:2222:3333:::5555:6666:7777:8888"], - [false , "1111:2222::4444:5555:6666:7777:8888:"], - [false , "1111:2222::4444:5555:6666:7777::"], - [false , "1111:2222::4444:5555:6666::8888"], - [false , "1111:2222::4444:5555::1.2.3.4"], - [false , "1111:2222::4444:5555::7777:8888"], - [false , "1111:2222::4444::6666:1.2.3.4"], - [false , "1111:2222::4444::6666:7777:8888"], - [false , "1111:2222::5555:"], - [false , "1111:2222::5555:6666:7777:8888:"], - [false , "1111:2222::6666:7777:8888:"], - [false , "1111:2222::7777:8888:"], - [false , "1111:2222::8888:"], - [false , "1111:2222:::"], - [false , "1111:2222:::4444:5555:6666:1.2.3.4"], - [false , "1111:2222:::4444:5555:6666:7777:8888"], - [false , "1111::3333:4444:5555:6666:7777:8888:"], - [false , "1111::3333:4444:5555:6666:7777::"], - [false , "1111::3333:4444:5555:6666::8888"], - [false , "1111::3333:4444:5555::1.2.3.4"], - [false , "1111::3333:4444:5555::7777:8888"], - [false , "1111::3333:4444::6666:1.2.3.4"], - [false , "1111::3333:4444::6666:7777:8888"], - [false , "1111::3333::5555:6666:1.2.3.4"], - [false , "1111::3333::5555:6666:7777:8888"], - [false , "1111::4444:5555:6666:7777:8888:"], - [false , "1111::5555:"], - [false , "1111::5555:6666:7777:8888:"], - [false , "1111::6666:7777:8888:"], - [false , "1111::7777:8888:"], - [false , "1111::8888:"], - [false , "1111:::"], - [false , "1111:::3333:4444:5555:6666:1.2.3.4"], - [false , "1111:::3333:4444:5555:6666:7777:8888"], - [false , "123"], - [false , "12345::6:7:8"], - [false , "192.168.0.256"], - [false , "192.168.256.0"], - [false , "1:2:3:4:5:6:7:8:9"], - [false , "1:2:3::4:5:6:7:8:9"], - [false , "1:2:3::4:5::7:8"], - [false , "1::1.2.256.4"], - [false , "1::1.2.3.256"], - [false , "1::1.2.3.300"], - [false , "1::1.2.3.900"], - [false , "1::1.2.300.4"], - [false , "1::1.2.900.4"], - [false , "1::1.256.3.4"], - [false , "1::1.300.3.4"], - [false , "1::1.900.3.4"], - [false , "1::256.2.3.4"], - [false , "1::260.2.3.4"], - [false , "1::2::3"], - [false , "1::300.2.3.4"], - [false , "1::300.300.300.300"], - [false , "1::3000.30.30.30"], - [false , "1::400.2.3.4"], - [false , "1::5:1.2.256.4"], - [false , "1::5:1.2.3.256"], - [false , "1::5:1.2.3.300"], - [false , "1::5:1.2.3.900"], - [false , "1::5:1.2.300.4"], - [false , "1::5:1.2.900.4"], - [false , "1::5:1.256.3.4"], - [false , "1::5:1.300.3.4"], - [false , "1::5:1.900.3.4"], - [false , "1::5:256.2.3.4"], - [false , "1::5:260.2.3.4"], - [false , "1::5:300.2.3.4"], - [false , "1::5:300.300.300.300"], - [false , "1::5:3000.30.30.30"], - [false , "1::5:400.2.3.4"], - [false , "1::5:900.2.3.4"], - [false , "1::900.2.3.4"], - [false , "1:::3:4:5"], - [false , "2001:0000:1234: 0000:0000:C1C0:ABCD:0876"], - [false , "2001:0000:1234:0000:00001:C1C0:ABCD:0876"], - [false , "2001:0000:1234:0000:0000:C1C0:ABCD:0876 0"], - [false , "2001:1:1:1:1:1:255Z255X255Y255"], - [false , "2001::FFD3::57ab"], - [false , "2001:DB8:0:0:8:800:200C:417A:221"], - [false , "2001:db8:85a3::8a2e:37023:7334"], - [false , "2001:db8:85a3::8a2e:370k:7334"], - [false , "255.256.255.255"], - [false , "256.255.255.255"], - [false , "3ffe:0b00:0000:0001:0000:0000:000a"], - [false , "3ffe:b00::1::a"], - [false , ":"], - [false , ":1.2.3.4"], - [false , ":1111:2222:3333:4444:5555:6666:1.2.3.4"], - [false , ":1111:2222:3333:4444:5555:6666:7777:8888"], - [false , ":1111:2222:3333:4444:5555:6666:7777::"], - [false , ":1111:2222:3333:4444:5555:6666::"], - [false , ":1111:2222:3333:4444:5555:6666::8888"], - [false , ":1111:2222:3333:4444:5555::"], - [false , ":1111:2222:3333:4444:5555::1.2.3.4"], - [false , ":1111:2222:3333:4444:5555::7777:8888"], - [false , ":1111:2222:3333:4444:5555::8888"], - [false , ":1111:2222:3333:4444::"], - [false , ":1111:2222:3333:4444::1.2.3.4"], - [false , ":1111:2222:3333:4444::5555"], - [false , ":1111:2222:3333:4444::6666:1.2.3.4"], - [false , ":1111:2222:3333:4444::6666:7777:8888"], - [false , ":1111:2222:3333:4444::7777:8888"], - [false , ":1111:2222:3333:4444::8888"], - [false , ":1111:2222:3333::"], - [false , ":1111:2222:3333::1.2.3.4"], - [false , ":1111:2222:3333::5555"], - [false , ":1111:2222:3333::5555:6666:1.2.3.4"], - [false , ":1111:2222:3333::5555:6666:7777:8888"], - [false , ":1111:2222:3333::6666:1.2.3.4"], - [false , ":1111:2222:3333::6666:7777:8888"], - [false , ":1111:2222:3333::7777:8888"], - [false , ":1111:2222:3333::8888"], - [false , ":1111:2222::"], - [false , ":1111:2222::1.2.3.4"], - [false , ":1111:2222::4444:5555:6666:1.2.3.4"], - [false , ":1111:2222::4444:5555:6666:7777:8888"], - [false , ":1111:2222::5555"], - [false , ":1111:2222::5555:6666:1.2.3.4"], - [false , ":1111:2222::5555:6666:7777:8888"], - [false , ":1111:2222::6666:1.2.3.4"], - [false , ":1111:2222::6666:7777:8888"], - [false , ":1111:2222::7777:8888"], - [false , ":1111:2222::8888"], - [false , ":1111::"], - [false , ":1111::1.2.3.4"], - [false , ":1111::3333:4444:5555:6666:1.2.3.4"], - [false , ":1111::3333:4444:5555:6666:7777:8888"], - [false , ":1111::4444:5555:6666:1.2.3.4"], - [false , ":1111::4444:5555:6666:7777:8888"], - [false , ":1111::5555"], - [false , ":1111::5555:6666:1.2.3.4"], - [false , ":1111::5555:6666:7777:8888"], - [false , ":1111::6666:1.2.3.4"], - [false , ":1111::6666:7777:8888"], - [false , ":1111::7777:8888"], - [false , ":1111::8888"], - [false , ":2222:3333:4444:5555:6666:1.2.3.4"], - [false , ":2222:3333:4444:5555:6666:7777:8888"], - [false , ":3333:4444:5555:6666:1.2.3.4"], - [false , ":3333:4444:5555:6666:7777:8888"], - [false , ":4444:5555:6666:1.2.3.4"], - [false , ":4444:5555:6666:7777:8888"], - [false , ":5555:6666:1.2.3.4"], - [false , ":5555:6666:7777:8888"], - [false , ":6666:1.2.3.4"], - [false , ":6666:7777:8888"], - [false , ":7777:8888"], - [false , ":8888"], - [false , "::."], - [false , "::.."], - [false , "::..."], - [false , "::...4"], - [false , "::..3."], - [false , "::..3.4"], - [false , "::.2.."], - [false , "::.2.3."], - [false , "::.2.3.4"], - [false , "::1..."], - [false , "::1.2.."], - [false , "::1.2.256.4"], - [false , "::1.2.3."], - [false , "::1.2.3.256"], - [false , "::1.2.3.300"], - [false , "::1.2.3.900"], - [false , "::1.2.300.4"], - [false , "::1.2.900.4"], - [false , "::1.256.3.4"], - [false , "::1.300.3.4"], - [false , "::1.900.3.4"], - [false , "::1111:2222:3333:4444:5555:6666::"], - [false , "::2222:3333:4444:5555:6666:7777:1.2.3.4"], - [false , "::2222:3333:4444:5555:6666:7777:8888:"], - [false , "::2222:3333:4444:5555:6666:7777:8888:9999"], - [false , "::2222:3333:4444:5555:7777:8888::"], - [false , "::2222:3333:4444:5555:7777::8888"], - [false , "::2222:3333:4444:5555::1.2.3.4"], - [false , "::2222:3333:4444:5555::7777:8888"], - [false , "::2222:3333:4444::6666:1.2.3.4"], - [false , "::2222:3333:4444::6666:7777:8888"], - [false , "::2222:3333::5555:6666:1.2.3.4"], - [false , "::2222:3333::5555:6666:7777:8888"], - [false , "::2222::4444:5555:6666:1.2.3.4"], - [false , "::2222::4444:5555:6666:7777:8888"], - [false , "::256.2.3.4"], - [false , "::260.2.3.4"], - [false , "::300.2.3.4"], - [false , "::300.300.300.300"], - [false , "::3000.30.30.30"], - [false , "::3333:4444:5555:6666:7777:8888:"], - [false , "::400.2.3.4"], - [false , "::4444:5555:6666:7777:8888:"], - [false , "::5555:"], - [false , "::5555:6666:7777:8888:"], - [false , "::6666:7777:8888:"], - [false , "::7777:8888:"], - [false , "::8888:"], - [false , "::900.2.3.4"], - [false , ":::"], - [false , ":::1.2.3.4"], - [false , ":::2222:3333:4444:5555:6666:1.2.3.4"], - [false , ":::2222:3333:4444:5555:6666:7777:8888"], - [false , ":::3333:4444:5555:6666:7777:8888"], - [false , ":::4444:5555:6666:1.2.3.4"], - [false , ":::4444:5555:6666:7777:8888"], - [false , ":::5555"], - [false , ":::5555:6666:1.2.3.4"], - [false , ":::5555:6666:7777:8888"], - [false , ":::6666:1.2.3.4"], - [false , ":::6666:7777:8888"], - [false , ":::7777:8888"], - [false , ":::8888"], - [false , "::ffff:192x168.1.26"], - [false , "::ffff:2.3.4"], - [false , "::ffff:257.1.2.3"], - [false , "FF01::101::2"], - [false , "FF02:0000:0000:0000:0000:0000:0000:0000:0001"], - [false , "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4"], - [false , "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX"], - [false , "fe80:0000:0000:0000:0204:61ff:254.157.241.086"], - [false , "fe80::4413:c8ae:2821:5852%10"], - [false , "ldkfj"], - [false , "mydomain.com"], - [false , "test.mydomain.com"], - [true , "0000:0000:0000:0000:0000:0000:0000:0000"], - [true , "0000:0000:0000:0000:0000:0000:0000:0001"], - [true , "0:0:0:0:0:0:0:0"], - [true , "0:0:0:0:0:0:0:1"], - [true , "0:0:0:0:0:0:0::"], - [true , "0:0:0:0:0:0:13.1.68.3"], - [true , "0:0:0:0:0:0::"], - [true , "0:0:0:0:0::"], - [true , "0:0:0:0:0:FFFF:129.144.52.38"], - [true , "0:0:0:0::"], - [true , "0:0:0::"], - [true , "0:0::"], - [true , "0::"], - [true , "0:a:b:c:d:e:f::"], - [true , "1.2.3.4"], - [true , "1111:2222:3333:4444:5555:6666:123.123.123.123"], - [true , "1111:2222:3333:4444:5555:6666:7777:8888"], - [true , "1111:2222:3333:4444:5555:6666:7777::"], - [true , "1111:2222:3333:4444:5555:6666::"], - [true , "1111:2222:3333:4444:5555:6666::8888"], - [true , "1111:2222:3333:4444:5555::"], - [true , "1111:2222:3333:4444:5555::123.123.123.123"], - [true , "1111:2222:3333:4444:5555::7777:8888"], - [true , "1111:2222:3333:4444:5555::8888"], - [true , "1111:2222:3333:4444::"], - [true , "1111:2222:3333:4444::123.123.123.123"], - [true , "1111:2222:3333:4444::6666:123.123.123.123"], - [true , "1111:2222:3333:4444::6666:7777:8888"], - [true , "1111:2222:3333:4444::7777:8888"], - [true , "1111:2222:3333:4444::8888"], - [true , "1111:2222:3333::"], - [true , "1111:2222:3333::123.123.123.123"], - [true , "1111:2222:3333::5555:6666:123.123.123.123"], - [true , "1111:2222:3333::5555:6666:7777:8888"], - [true , "1111:2222:3333::6666:123.123.123.123"], - [true , "1111:2222:3333::6666:7777:8888"], - [true , "1111:2222:3333::7777:8888"], - [true , "1111:2222:3333::8888"], - [true , "1111:2222::"], - [true , "1111:2222::123.123.123.123"], - [true , "1111:2222::4444:5555:6666:123.123.123.123"], - [true , "1111:2222::4444:5555:6666:7777:8888"], - [true , "1111:2222::5555:6666:123.123.123.123"], - [true , "1111:2222::5555:6666:7777:8888"], - [true , "1111:2222::6666:123.123.123.123"], - [true , "1111:2222::6666:7777:8888"], - [true , "1111:2222::7777:8888"], - [true , "1111:2222::8888"], - [true , "1111::"], - [true , "1111::123.123.123.123"], - [true , "1111::3333:4444:5555:6666:123.123.123.123"], - [true , "1111::3333:4444:5555:6666:7777:8888"], - [true , "1111::4444:5555:6666:123.123.123.123"], - [true , "1111::4444:5555:6666:7777:8888"], - [true , "1111::5555:6666:123.123.123.123"], - [true , "1111::5555:6666:7777:8888"], - [true , "1111::6666:123.123.123.123"], - [true , "1111::6666:7777:8888"], - [true , "1111::7777:8888"], - [true , "1111::8888"], - [true , "123.23.34.2"], - [true , "172.26.168.134"], - [true , "192.168.0.0"], - [true , "192.168.128.255"], - [true , "1:2:3:4:5:6:1.2.3.4"], - [true , "1:2:3:4:5:6:7:8"], - [true , "1:2:3:4:5:6::"], - [true , "1:2:3:4:5:6::8"], - [true , "1:2:3:4:5::"], - [true , "1:2:3:4:5::1.2.3.4"], - [true , "1:2:3:4:5::7:8"], - [true , "1:2:3:4:5::8"], - [true , "1:2:3:4::"], - [true , "1:2:3:4::1.2.3.4"], - [true , "1:2:3:4::5:1.2.3.4"], - [true , "1:2:3:4::7:8"], - [true , "1:2:3:4::8"], - [true , "1:2:3::"], - [true , "1:2:3::1.2.3.4"], - [true , "1:2:3::5:1.2.3.4"], - [true , "1:2:3::7:8"], - [true , "1:2:3::8"], - [true , "1:2::"], - [true , "1:2::1.2.3.4"], - [true , "1:2::5:1.2.3.4"], - [true , "1:2::7:8"], - [true , "1:2::8"], - [true , "1::"], - [true , "1::1.2.3.4"], - [true , "1::2:3"], - [true , "1::2:3:4"], - [true , "1::2:3:4:5"], - [true , "1::2:3:4:5:6"], - [true , "1::2:3:4:5:6:7"], - [true , "1::5:1.2.3.4"], - [true , "1::5:11.22.33.44"], - [true , "1::7:8"], - [true , "1::8"], - [true , "2001:0000:1234:0000:0000:C1C0:ABCD:0876"], - [true , "2001:0:1234::C1C0:ABCD:876"], - [true , "2001:0db8:0000:0000:0000:0000:1428:57ab"], - [true , "2001:0db8:0000:0000:0000::1428:57ab"], - [true , "2001:0db8:0:0:0:0:1428:57ab"], - [true , "2001:0db8:0:0::1428:57ab"], - [true , "2001:0db8:1234:0000:0000:0000:0000:0000"], - [true , "2001:0db8:1234::"], - [true , "2001:0db8:1234:ffff:ffff:ffff:ffff:ffff"], - [true , "2001:0db8:85a3:0000:0000:8a2e:0370:7334"], - [true , "2001:0db8::1428:57ab"], - [true , "2001:2:3:4:5:6:7:134"], - [true , "2001:DB8:0:0:8:800:200C:417A"], - [true , "2001:DB8::8:800:200C:417A"], - [true , "2001:db8:85a3:0:0:8a2e:370:7334"], - [true , "2001:db8:85a3::8a2e:370:7334"], - [true , "2001:db8::"], - [true , "2001:db8::1428:57ab"], - [true , "2001:db8:a::123"], - [true , "2002::"], - [true , "2::10"], - [true , "3ffe:0b00:0000:0000:0001:0000:0000:000a"], - [true , "3ffe:b00::1:0:0:a"], - [true , "::"], - [true , "::0"], - [true , "::0:0"], - [true , "::0:0:0"], - [true , "::0:0:0:0"], - [true , "::0:0:0:0:0"], - [true , "::0:0:0:0:0:0"], - [true , "::0:0:0:0:0:0:0"], - [true , "::0:a:b:c:d:e:f"], - [true , "::1"], - [true , "::123.123.123.123"], - [true , "::13.1.68.3"], - [true , "::2222:3333:4444:5555:6666:123.123.123.123"], - [true , "::2222:3333:4444:5555:6666:7777:8888"], - [true , "::2:3"], - [true , "::2:3:4"], - [true , "::2:3:4:5"], - [true , "::2:3:4:5:6"], - [true , "::2:3:4:5:6:7"], - [true , "::2:3:4:5:6:7:8"], - [true , "::3333:4444:5555:6666:7777:8888"], - [true , "::4444:5555:6666:123.123.123.123"], - [true , "::4444:5555:6666:7777:8888"], - [true , "::5555:6666:123.123.123.123"], - [true , "::5555:6666:7777:8888"], - [true , "::6666:123.123.123.123"], - [true , "::6666:7777:8888"], - [true , "::7777:8888"], - [true , "::8"], - [true , "::8888"], - [true , "::FFFF:129.144.52.38"], - [true , "::ffff:0:0"], - [true , "::ffff:0c22:384e"], - [true , "::ffff:12.34.56.78"], - [true , "::ffff:192.0.2.128"], - [true , "::ffff:192.168.1.1"], - [true , "::ffff:192.168.1.26"], - [true , "::ffff:c000:280"], - [true , "FF01:0:0:0:0:0:0:101"], - [true , "FF01::101"], - [true , "FF02:0000:0000:0000:0000:0000:0000:0001"], - [true , "FF02::1"], - [true , "a:b:c:d:e:f:0::"], - [true , "fe80:0000:0000:0000:0204:61ff:fe9d:f156"], - [true , "fe80:0:0:0:204:61ff:254.157.241.86"], - [true , "fe80:0:0:0:204:61ff:fe9d:f156"], - [true , "fe80::"], - [true , "fe80::1"], - [true , "fe80::204:61ff:254.157.241.86"], - [true , "fe80::204:61ff:fe9d:f156"], - [true , "fe80::217:f2ff:254.7.237.98"], - [true , "fe80::217:f2ff:fe07:ed62"], - [true , "ff02::1"] -]; + [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876 '], + [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0'], + [false, ' 2001:0000:1234:0000:0000:C1C0:ABCD:0876'], + [false, ' 2001:0:1234::C1C0:ABCD:876 '], + [false, ' 2001:0:1234::C1C0:ABCD:876'], + [false, ''], + [false, "':10.0.0.1"], + [false, '---'], + [false, '02001:0000:1234:0000:0000:C1C0:ABCD:0876'], + [false, '1.2.3.4:1111:2222:3333:4444::5555'], + [false, '1.2.3.4:1111:2222:3333::5555'], + [false, '1.2.3.4:1111:2222::5555'], + [false, '1.2.3.4:1111::5555'], + [false, '1.2.3.4::'], + [false, '1.2.3.4::5555'], + [false, '1111'], + [false, '11112222:3333:4444:5555:6666:1.2.3.4'], + [false, '11112222:3333:4444:5555:6666:7777:8888'], + [false, '1111:'], + [false, '1111:1.2.3.4'], + [false, '1111:2222'], + [false, '1111:22223333:4444:5555:6666:1.2.3.4'], + [false, '1111:22223333:4444:5555:6666:7777:8888'], + [false, '1111:2222:'], + [false, '1111:2222:1.2.3.4'], + [false, '1111:2222:3333'], + [false, '1111:2222:33334444:5555:6666:1.2.3.4'], + [false, '1111:2222:33334444:5555:6666:7777:8888'], + [false, '1111:2222:3333:'], + [false, '1111:2222:3333:1.2.3.4'], + [false, '1111:2222:3333:4444'], + [false, '1111:2222:3333:44445555:6666:1.2.3.4'], + [false, '1111:2222:3333:44445555:6666:7777:8888'], + [false, '1111:2222:3333:4444:'], + [false, '1111:2222:3333:4444:1.2.3.4'], + [false, '1111:2222:3333:4444:5555'], + [false, '1111:2222:3333:4444:55556666:1.2.3.4'], + [false, '1111:2222:3333:4444:55556666:7777:8888'], + [false, '1111:2222:3333:4444:5555:'], + [false, '1111:2222:3333:4444:5555:1.2.3.4'], + [false, '1111:2222:3333:4444:5555:6666'], + [false, '1111:2222:3333:4444:5555:66661.2.3.4'], + [false, '1111:2222:3333:4444:5555:66667777:8888'], + [false, '1111:2222:3333:4444:5555:6666:'], + [false, '1111:2222:3333:4444:5555:6666:00.00.00.00'], + [false, '1111:2222:3333:4444:5555:6666:000.000.000.000'], + [false, '1111:2222:3333:4444:5555:6666:1.2.3.4.5'], + [false, '1111:2222:3333:4444:5555:6666:255.255.255255'], + [false, '1111:2222:3333:4444:5555:6666:255.255255.255'], + [false, '1111:2222:3333:4444:5555:6666:255255.255.255'], + [false, '1111:2222:3333:4444:5555:6666:256.256.256.256'], + [false, '1111:2222:3333:4444:5555:6666:7777'], + [false, '1111:2222:3333:4444:5555:6666:77778888'], + [false, '1111:2222:3333:4444:5555:6666:7777:'], + [false, '1111:2222:3333:4444:5555:6666:7777:1.2.3.4'], + [false, '1111:2222:3333:4444:5555:6666:7777:8888:'], + [false, '1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4'], + [false, '1111:2222:3333:4444:5555:6666:7777:8888:9999'], + [false, '1111:2222:3333:4444:5555:6666:7777:8888::'], + [false, '1111:2222:3333:4444:5555:6666:7777:::'], + [false, '1111:2222:3333:4444:5555:6666::1.2.3.4'], + [false, '1111:2222:3333:4444:5555:6666::8888:'], + [false, '1111:2222:3333:4444:5555:6666:::'], + [false, '1111:2222:3333:4444:5555:6666:::8888'], + [false, '1111:2222:3333:4444:5555::7777:8888:'], + [false, '1111:2222:3333:4444:5555::7777::'], + [false, '1111:2222:3333:4444:5555::8888:'], + [false, '1111:2222:3333:4444:5555:::'], + [false, '1111:2222:3333:4444:5555:::1.2.3.4'], + [false, '1111:2222:3333:4444:5555:::7777:8888'], + [false, '1111:2222:3333:4444::5555:'], + [false, '1111:2222:3333:4444::6666:7777:8888:'], + [false, '1111:2222:3333:4444::6666:7777::'], + [false, '1111:2222:3333:4444::6666::8888'], + [false, '1111:2222:3333:4444::7777:8888:'], + [false, '1111:2222:3333:4444::8888:'], + [false, '1111:2222:3333:4444:::'], + [false, '1111:2222:3333:4444:::6666:1.2.3.4'], + [false, '1111:2222:3333:4444:::6666:7777:8888'], + [false, '1111:2222:3333::5555:'], + [false, '1111:2222:3333::5555:6666:7777:8888:'], + [false, '1111:2222:3333::5555:6666:7777::'], + [false, '1111:2222:3333::5555:6666::8888'], + [false, '1111:2222:3333::5555::1.2.3.4'], + [false, '1111:2222:3333::5555::7777:8888'], + [false, '1111:2222:3333::6666:7777:8888:'], + [false, '1111:2222:3333::7777:8888:'], + [false, '1111:2222:3333::8888:'], + [false, '1111:2222:3333:::'], + [false, '1111:2222:3333:::5555:6666:1.2.3.4'], + [false, '1111:2222:3333:::5555:6666:7777:8888'], + [false, '1111:2222::4444:5555:6666:7777:8888:'], + [false, '1111:2222::4444:5555:6666:7777::'], + [false, '1111:2222::4444:5555:6666::8888'], + [false, '1111:2222::4444:5555::1.2.3.4'], + [false, '1111:2222::4444:5555::7777:8888'], + [false, '1111:2222::4444::6666:1.2.3.4'], + [false, '1111:2222::4444::6666:7777:8888'], + [false, '1111:2222::5555:'], + [false, '1111:2222::5555:6666:7777:8888:'], + [false, '1111:2222::6666:7777:8888:'], + [false, '1111:2222::7777:8888:'], + [false, '1111:2222::8888:'], + [false, '1111:2222:::'], + [false, '1111:2222:::4444:5555:6666:1.2.3.4'], + [false, '1111:2222:::4444:5555:6666:7777:8888'], + [false, '1111::3333:4444:5555:6666:7777:8888:'], + [false, '1111::3333:4444:5555:6666:7777::'], + [false, '1111::3333:4444:5555:6666::8888'], + [false, '1111::3333:4444:5555::1.2.3.4'], + [false, '1111::3333:4444:5555::7777:8888'], + [false, '1111::3333:4444::6666:1.2.3.4'], + [false, '1111::3333:4444::6666:7777:8888'], + [false, '1111::3333::5555:6666:1.2.3.4'], + [false, '1111::3333::5555:6666:7777:8888'], + [false, '1111::4444:5555:6666:7777:8888:'], + [false, '1111::5555:'], + [false, '1111::5555:6666:7777:8888:'], + [false, '1111::6666:7777:8888:'], + [false, '1111::7777:8888:'], + [false, '1111::8888:'], + [false, '1111:::'], + [false, '1111:::3333:4444:5555:6666:1.2.3.4'], + [false, '1111:::3333:4444:5555:6666:7777:8888'], + [false, '123'], + [false, '12345::6:7:8'], + [false, '192.168.0.256'], + [false, '192.168.256.0'], + [false, '1:2:3:4:5:6:7:8:9'], + [false, '1:2:3::4:5:6:7:8:9'], + [false, '1:2:3::4:5::7:8'], + [false, '1::1.2.256.4'], + [false, '1::1.2.3.256'], + [false, '1::1.2.3.300'], + [false, '1::1.2.3.900'], + [false, '1::1.2.300.4'], + [false, '1::1.2.900.4'], + [false, '1::1.256.3.4'], + [false, '1::1.300.3.4'], + [false, '1::1.900.3.4'], + [false, '1::256.2.3.4'], + [false, '1::260.2.3.4'], + [false, '1::2::3'], + [false, '1::300.2.3.4'], + [false, '1::300.300.300.300'], + [false, '1::3000.30.30.30'], + [false, '1::400.2.3.4'], + [false, '1::5:1.2.256.4'], + [false, '1::5:1.2.3.256'], + [false, '1::5:1.2.3.300'], + [false, '1::5:1.2.3.900'], + [false, '1::5:1.2.300.4'], + [false, '1::5:1.2.900.4'], + [false, '1::5:1.256.3.4'], + [false, '1::5:1.300.3.4'], + [false, '1::5:1.900.3.4'], + [false, '1::5:256.2.3.4'], + [false, '1::5:260.2.3.4'], + [false, '1::5:300.2.3.4'], + [false, '1::5:300.300.300.300'], + [false, '1::5:3000.30.30.30'], + [false, '1::5:400.2.3.4'], + [false, '1::5:900.2.3.4'], + [false, '1::900.2.3.4'], + [false, '1:::3:4:5'], + [false, '2001:0000:1234: 0000:0000:C1C0:ABCD:0876'], + [false, '2001:0000:1234:0000:00001:C1C0:ABCD:0876'], + [false, '2001:0000:1234:0000:0000:C1C0:ABCD:0876 0'], + [false, '2001:1:1:1:1:1:255Z255X255Y255'], + [false, '2001::FFD3::57ab'], + [false, '2001:DB8:0:0:8:800:200C:417A:221'], + [false, '2001:db8:85a3::8a2e:37023:7334'], + [false, '2001:db8:85a3::8a2e:370k:7334'], + [false, '255.256.255.255'], + [false, '256.255.255.255'], + [false, '3ffe:0b00:0000:0001:0000:0000:000a'], + [false, '3ffe:b00::1::a'], + [false, ':'], + [false, ':1.2.3.4'], + [false, ':1111:2222:3333:4444:5555:6666:1.2.3.4'], + [false, ':1111:2222:3333:4444:5555:6666:7777:8888'], + [false, ':1111:2222:3333:4444:5555:6666:7777::'], + [false, ':1111:2222:3333:4444:5555:6666::'], + [false, ':1111:2222:3333:4444:5555:6666::8888'], + [false, ':1111:2222:3333:4444:5555::'], + [false, ':1111:2222:3333:4444:5555::1.2.3.4'], + [false, ':1111:2222:3333:4444:5555::7777:8888'], + [false, ':1111:2222:3333:4444:5555::8888'], + [false, ':1111:2222:3333:4444::'], + [false, ':1111:2222:3333:4444::1.2.3.4'], + [false, ':1111:2222:3333:4444::5555'], + [false, ':1111:2222:3333:4444::6666:1.2.3.4'], + [false, ':1111:2222:3333:4444::6666:7777:8888'], + [false, ':1111:2222:3333:4444::7777:8888'], + [false, ':1111:2222:3333:4444::8888'], + [false, ':1111:2222:3333::'], + [false, ':1111:2222:3333::1.2.3.4'], + [false, ':1111:2222:3333::5555'], + [false, ':1111:2222:3333::5555:6666:1.2.3.4'], + [false, ':1111:2222:3333::5555:6666:7777:8888'], + [false, ':1111:2222:3333::6666:1.2.3.4'], + [false, ':1111:2222:3333::6666:7777:8888'], + [false, ':1111:2222:3333::7777:8888'], + [false, ':1111:2222:3333::8888'], + [false, ':1111:2222::'], + [false, ':1111:2222::1.2.3.4'], + [false, ':1111:2222::4444:5555:6666:1.2.3.4'], + [false, ':1111:2222::4444:5555:6666:7777:8888'], + [false, ':1111:2222::5555'], + [false, ':1111:2222::5555:6666:1.2.3.4'], + [false, ':1111:2222::5555:6666:7777:8888'], + [false, ':1111:2222::6666:1.2.3.4'], + [false, ':1111:2222::6666:7777:8888'], + [false, ':1111:2222::7777:8888'], + [false, ':1111:2222::8888'], + [false, ':1111::'], + [false, ':1111::1.2.3.4'], + [false, ':1111::3333:4444:5555:6666:1.2.3.4'], + [false, ':1111::3333:4444:5555:6666:7777:8888'], + [false, ':1111::4444:5555:6666:1.2.3.4'], + [false, ':1111::4444:5555:6666:7777:8888'], + [false, ':1111::5555'], + [false, ':1111::5555:6666:1.2.3.4'], + [false, ':1111::5555:6666:7777:8888'], + [false, ':1111::6666:1.2.3.4'], + [false, ':1111::6666:7777:8888'], + [false, ':1111::7777:8888'], + [false, ':1111::8888'], + [false, ':2222:3333:4444:5555:6666:1.2.3.4'], + [false, ':2222:3333:4444:5555:6666:7777:8888'], + [false, ':3333:4444:5555:6666:1.2.3.4'], + [false, ':3333:4444:5555:6666:7777:8888'], + [false, ':4444:5555:6666:1.2.3.4'], + [false, ':4444:5555:6666:7777:8888'], + [false, ':5555:6666:1.2.3.4'], + [false, ':5555:6666:7777:8888'], + [false, ':6666:1.2.3.4'], + [false, ':6666:7777:8888'], + [false, ':7777:8888'], + [false, ':8888'], + [false, '::.'], + [false, '::..'], + [false, '::...'], + [false, '::...4'], + [false, '::..3.'], + [false, '::..3.4'], + [false, '::.2..'], + [false, '::.2.3.'], + [false, '::.2.3.4'], + [false, '::1...'], + [false, '::1.2..'], + [false, '::1.2.256.4'], + [false, '::1.2.3.'], + [false, '::1.2.3.256'], + [false, '::1.2.3.300'], + [false, '::1.2.3.900'], + [false, '::1.2.300.4'], + [false, '::1.2.900.4'], + [false, '::1.256.3.4'], + [false, '::1.300.3.4'], + [false, '::1.900.3.4'], + [false, '::1111:2222:3333:4444:5555:6666::'], + [false, '::2222:3333:4444:5555:6666:7777:1.2.3.4'], + [false, '::2222:3333:4444:5555:6666:7777:8888:'], + [false, '::2222:3333:4444:5555:6666:7777:8888:9999'], + [false, '::2222:3333:4444:5555:7777:8888::'], + [false, '::2222:3333:4444:5555:7777::8888'], + [false, '::2222:3333:4444:5555::1.2.3.4'], + [false, '::2222:3333:4444:5555::7777:8888'], + [false, '::2222:3333:4444::6666:1.2.3.4'], + [false, '::2222:3333:4444::6666:7777:8888'], + [false, '::2222:3333::5555:6666:1.2.3.4'], + [false, '::2222:3333::5555:6666:7777:8888'], + [false, '::2222::4444:5555:6666:1.2.3.4'], + [false, '::2222::4444:5555:6666:7777:8888'], + [false, '::256.2.3.4'], + [false, '::260.2.3.4'], + [false, '::300.2.3.4'], + [false, '::300.300.300.300'], + [false, '::3000.30.30.30'], + [false, '::3333:4444:5555:6666:7777:8888:'], + [false, '::400.2.3.4'], + [false, '::4444:5555:6666:7777:8888:'], + [false, '::5555:'], + [false, '::5555:6666:7777:8888:'], + [false, '::6666:7777:8888:'], + [false, '::7777:8888:'], + [false, '::8888:'], + [false, '::900.2.3.4'], + [false, ':::'], + [false, ':::1.2.3.4'], + [false, ':::2222:3333:4444:5555:6666:1.2.3.4'], + [false, ':::2222:3333:4444:5555:6666:7777:8888'], + [false, ':::3333:4444:5555:6666:7777:8888'], + [false, ':::4444:5555:6666:1.2.3.4'], + [false, ':::4444:5555:6666:7777:8888'], + [false, ':::5555'], + [false, ':::5555:6666:1.2.3.4'], + [false, ':::5555:6666:7777:8888'], + [false, ':::6666:1.2.3.4'], + [false, ':::6666:7777:8888'], + [false, ':::7777:8888'], + [false, ':::8888'], + [false, '::ffff:192x168.1.26'], + [false, '::ffff:2.3.4'], + [false, '::ffff:257.1.2.3'], + [false, 'FF01::101::2'], + [false, 'FF02:0000:0000:0000:0000:0000:0000:0000:0001'], + [false, 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4'], + [false, 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX'], + [false, 'fe80:0000:0000:0000:0204:61ff:254.157.241.086'], + [false, 'fe80::4413:c8ae:2821:5852%10'], + [false, 'ldkfj'], + [false, 'mydomain.com'], + [false, 'test.mydomain.com'], + [true, '0000:0000:0000:0000:0000:0000:0000:0000'], + [true, '0000:0000:0000:0000:0000:0000:0000:0001'], + [true, '0:0:0:0:0:0:0:0'], + [true, '0:0:0:0:0:0:0:1'], + [true, '0:0:0:0:0:0:0::'], + [true, '0:0:0:0:0:0:13.1.68.3'], + [true, '0:0:0:0:0:0::'], + [true, '0:0:0:0:0::'], + [true, '0:0:0:0:0:FFFF:129.144.52.38'], + [true, '0:0:0:0::'], + [true, '0:0:0::'], + [true, '0:0::'], + [true, '0::'], + [true, '0:a:b:c:d:e:f::'], + [true, '1.2.3.4'], + [true, '1111:2222:3333:4444:5555:6666:123.123.123.123'], + [true, '1111:2222:3333:4444:5555:6666:7777:8888'], + [true, '1111:2222:3333:4444:5555:6666:7777::'], + [true, '1111:2222:3333:4444:5555:6666::'], + [true, '1111:2222:3333:4444:5555:6666::8888'], + [true, '1111:2222:3333:4444:5555::'], + [true, '1111:2222:3333:4444:5555::123.123.123.123'], + [true, '1111:2222:3333:4444:5555::7777:8888'], + [true, '1111:2222:3333:4444:5555::8888'], + [true, '1111:2222:3333:4444::'], + [true, '1111:2222:3333:4444::123.123.123.123'], + [true, '1111:2222:3333:4444::6666:123.123.123.123'], + [true, '1111:2222:3333:4444::6666:7777:8888'], + [true, '1111:2222:3333:4444::7777:8888'], + [true, '1111:2222:3333:4444::8888'], + [true, '1111:2222:3333::'], + [true, '1111:2222:3333::123.123.123.123'], + [true, '1111:2222:3333::5555:6666:123.123.123.123'], + [true, '1111:2222:3333::5555:6666:7777:8888'], + [true, '1111:2222:3333::6666:123.123.123.123'], + [true, '1111:2222:3333::6666:7777:8888'], + [true, '1111:2222:3333::7777:8888'], + [true, '1111:2222:3333::8888'], + [true, '1111:2222::'], + [true, '1111:2222::123.123.123.123'], + [true, '1111:2222::4444:5555:6666:123.123.123.123'], + [true, '1111:2222::4444:5555:6666:7777:8888'], + [true, '1111:2222::5555:6666:123.123.123.123'], + [true, '1111:2222::5555:6666:7777:8888'], + [true, '1111:2222::6666:123.123.123.123'], + [true, '1111:2222::6666:7777:8888'], + [true, '1111:2222::7777:8888'], + [true, '1111:2222::8888'], + [true, '1111::'], + [true, '1111::123.123.123.123'], + [true, '1111::3333:4444:5555:6666:123.123.123.123'], + [true, '1111::3333:4444:5555:6666:7777:8888'], + [true, '1111::4444:5555:6666:123.123.123.123'], + [true, '1111::4444:5555:6666:7777:8888'], + [true, '1111::5555:6666:123.123.123.123'], + [true, '1111::5555:6666:7777:8888'], + [true, '1111::6666:123.123.123.123'], + [true, '1111::6666:7777:8888'], + [true, '1111::7777:8888'], + [true, '1111::8888'], + [true, '123.23.34.2'], + [true, '172.26.168.134'], + [true, '192.168.0.0'], + [true, '192.168.128.255'], + [true, '1:2:3:4:5:6:1.2.3.4'], + [true, '1:2:3:4:5:6:7:8'], + [true, '1:2:3:4:5:6::'], + [true, '1:2:3:4:5:6::8'], + [true, '1:2:3:4:5::'], + [true, '1:2:3:4:5::1.2.3.4'], + [true, '1:2:3:4:5::7:8'], + [true, '1:2:3:4:5::8'], + [true, '1:2:3:4::'], + [true, '1:2:3:4::1.2.3.4'], + [true, '1:2:3:4::5:1.2.3.4'], + [true, '1:2:3:4::7:8'], + [true, '1:2:3:4::8'], + [true, '1:2:3::'], + [true, '1:2:3::1.2.3.4'], + [true, '1:2:3::5:1.2.3.4'], + [true, '1:2:3::7:8'], + [true, '1:2:3::8'], + [true, '1:2::'], + [true, '1:2::1.2.3.4'], + [true, '1:2::5:1.2.3.4'], + [true, '1:2::7:8'], + [true, '1:2::8'], + [true, '1::'], + [true, '1::1.2.3.4'], + [true, '1::2:3'], + [true, '1::2:3:4'], + [true, '1::2:3:4:5'], + [true, '1::2:3:4:5:6'], + [true, '1::2:3:4:5:6:7'], + [true, '1::5:1.2.3.4'], + [true, '1::5:11.22.33.44'], + [true, '1::7:8'], + [true, '1::8'], + [true, '2001:0000:1234:0000:0000:C1C0:ABCD:0876'], + [true, '2001:0:1234::C1C0:ABCD:876'], + [true, '2001:0db8:0000:0000:0000:0000:1428:57ab'], + [true, '2001:0db8:0000:0000:0000::1428:57ab'], + [true, '2001:0db8:0:0:0:0:1428:57ab'], + [true, '2001:0db8:0:0::1428:57ab'], + [true, '2001:0db8:1234:0000:0000:0000:0000:0000'], + [true, '2001:0db8:1234::'], + [true, '2001:0db8:1234:ffff:ffff:ffff:ffff:ffff'], + [true, '2001:0db8:85a3:0000:0000:8a2e:0370:7334'], + [true, '2001:0db8::1428:57ab'], + [true, '2001:2:3:4:5:6:7:134'], + [true, '2001:DB8:0:0:8:800:200C:417A'], + [true, '2001:DB8::8:800:200C:417A'], + [true, '2001:db8:85a3:0:0:8a2e:370:7334'], + [true, '2001:db8:85a3::8a2e:370:7334'], + [true, '2001:db8::'], + [true, '2001:db8::1428:57ab'], + [true, '2001:db8:a::123'], + [true, '2002::'], + [true, '2::10'], + [true, '3ffe:0b00:0000:0000:0001:0000:0000:000a'], + [true, '3ffe:b00::1:0:0:a'], + [true, '::'], + [true, '::0'], + [true, '::0:0'], + [true, '::0:0:0'], + [true, '::0:0:0:0'], + [true, '::0:0:0:0:0'], + [true, '::0:0:0:0:0:0'], + [true, '::0:0:0:0:0:0:0'], + [true, '::0:a:b:c:d:e:f'], + [true, '::1'], + [true, '::123.123.123.123'], + [true, '::13.1.68.3'], + [true, '::2222:3333:4444:5555:6666:123.123.123.123'], + [true, '::2222:3333:4444:5555:6666:7777:8888'], + [true, '::2:3'], + [true, '::2:3:4'], + [true, '::2:3:4:5'], + [true, '::2:3:4:5:6'], + [true, '::2:3:4:5:6:7'], + [true, '::2:3:4:5:6:7:8'], + [true, '::3333:4444:5555:6666:7777:8888'], + [true, '::4444:5555:6666:123.123.123.123'], + [true, '::4444:5555:6666:7777:8888'], + [true, '::5555:6666:123.123.123.123'], + [true, '::5555:6666:7777:8888'], + [true, '::6666:123.123.123.123'], + [true, '::6666:7777:8888'], + [true, '::7777:8888'], + [true, '::8'], + [true, '::8888'], + [true, '::FFFF:129.144.52.38'], + [true, '::ffff:0:0'], + [true, '::ffff:0c22:384e'], + [true, '::ffff:12.34.56.78'], + [true, '::ffff:192.0.2.128'], + [true, '::ffff:192.168.1.1'], + [true, '::ffff:192.168.1.26'], + [true, '::ffff:c000:280'], + [true, 'FF01:0:0:0:0:0:0:101'], + [true, 'FF01::101'], + [true, 'FF02:0000:0000:0000:0000:0000:0000:0001'], + [true, 'FF02::1'], + [true, 'a:b:c:d:e:f:0::'], + [true, 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'], + [true, 'fe80:0:0:0:204:61ff:254.157.241.86'], + [true, 'fe80:0:0:0:204:61ff:fe9d:f156'], + [true, 'fe80::'], + [true, 'fe80::1'], + [true, 'fe80::204:61ff:254.157.241.86'], + [true, 'fe80::204:61ff:fe9d:f156'], + [true, 'fe80::217:f2ff:254.7.237.98'], + [true, 'fe80::217:f2ff:fe07:ed62'], + [true, 'ff02::1'], +] describe('get_ipany_re', function () { it('IPv6, Prefix', function (done) { // for x-*-ip headers // it must fail as of not valide - assert.ok(!net.isIPv6('IPv6:2001:db8:85a3::8a2e:370:7334')); + assert.ok(!net.isIPv6('IPv6:2001:db8:85a3::8a2e:370:7334')) // must okay! - assert.ok(net.isIPv6('2001:db8:85a3::8a2e:370:7334')); - done(); + assert.ok(net.isIPv6('2001:db8:85a3::8a2e:370:7334')) + done() }) it('IP fixtures check', function (done) { for (const i in ip_fixtures) { - const match = net_utils.get_ipany_re('^','$').test(ip_fixtures[i][1]); + const match = net_utils.get_ipany_re('^', '$').test(ip_fixtures[i][1]) // console.log('IP:', `'${ip_fixtures[i][1]}'` , 'Expected:', ip_fixtures[i][0] , 'Match:' , match); - assert.ok((match===ip_fixtures[i][0]), `${ip_fixtures[i][1]} - Expected: ${ip_fixtures[i][0]} - Match: ${match}`); + assert.ok( + match === ip_fixtures[i][0], + `${ip_fixtures[i][1]} - Expected: ${ip_fixtures[i][0]} - Match: ${match}`, + ) } - done(); + done() }) it('IPv4, bare', function (done) { // for x-*-ip headers - const match = net_utils.get_ipany_re().exec('127.0.0.1'); - assert.equal(match[1], '127.0.0.1'); - assert.equal(match.length, 2); - done(); + const match = net_utils.get_ipany_re().exec('127.0.0.1') + assert.equal(match[1], '127.0.0.1') + assert.equal(match.length, 2) + done() }) it('IPv4, Received header, parens', function (done) { - const received_re = net_utils.get_ipany_re('^Received:.*?[\\[\\(]', '[\\]\\)]'); - const match = received_re.exec('Received: from unknown (HELO mail.theartfarm.com) (127.0.0.30) by mail.theartfarm.com with SMTP; 5 Sep 2015 14:29:00 -0000'); - assert.equal(match[1], '127.0.0.30'); - assert.equal(match.length, 2); - done(); + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(]', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from unknown (HELO mail.theartfarm.com) (127.0.0.30) by mail.theartfarm.com with SMTP; 5 Sep 2015 14:29:00 -0000', + ) + assert.equal(match[1], '127.0.0.30') + assert.equal(match.length, 2) + done() }) it('IPv4, Received header, bracketed, expedia', function (done) { - const received_header = 'Received: from mta2.expediamail.com (mta2.expediamail.com [66.231.89.19]) by mail.theartfarm.com (Haraka/2.6.2-toaster) with ESMTPS id C669CF18-1C1C-484C-8A5B-A89088B048CB.1 envelope-from (version=TLSv1/SSLv3 cipher=AES256-SHA verify=NO); Sat, 05 Sep 2015 07:28:57 -0700'; - const received_re = net_utils.get_ipany_re('^Received:.*?[\\[\\(]', '[\\]\\)]'); - const match = received_re.exec(received_header); - assert.equal(match[1], '66.231.89.19'); - assert.equal(match.length, 2); - done(); + const received_header = + 'Received: from mta2.expediamail.com (mta2.expediamail.com [66.231.89.19]) by mail.theartfarm.com (Haraka/2.6.2-toaster) with ESMTPS id C669CF18-1C1C-484C-8A5B-A89088B048CB.1 envelope-from (version=TLSv1/SSLv3 cipher=AES256-SHA verify=NO); Sat, 05 Sep 2015 07:28:57 -0700' + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(]', + '[\\]\\)]', + ) + const match = received_re.exec(received_header) + assert.equal(match[1], '66.231.89.19') + assert.equal(match.length, 2) + done() }) it('IPv4, Received header, bracketed, github', function (done) { - const received_re = net_utils.get_ipany_re('^Received:.*?[\\[\\(]', '[\\]\\)]'); - const match = received_re.exec('Received: from github-smtp2a-ext-cp1-prd.iad.github.net (github-smtp2-ext5.iad.github.net [192.30.252.196])'); - assert.equal(match[1], '192.30.252.196'); - assert.equal(match.length, 2); - done(); + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(]', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from github-smtp2a-ext-cp1-prd.iad.github.net (github-smtp2-ext5.iad.github.net [192.30.252.196])', + ) + assert.equal(match[1], '192.30.252.196') + assert.equal(match.length, 2) + done() }) it('IPv6, Received header, bracketed', function (done) { - const received_header = 'Received: from ?IPv6:2601:184:c001:5cf7:a53f:baf7:aaf3:bce7? ([2601:184:c001:5cf7:a53f:baf7:aaf3:bce7])'; - const received_re = net_utils.get_ipany_re('^Received:.*?[\\[\\(]', '[\\]\\)]'); - const match = received_re.exec(received_header); - assert.equal(match[1], '2601:184:c001:5cf7:a53f:baf7:aaf3:bce7'); - assert.equal(match.length, 2); - done(); + const received_header = + 'Received: from ?IPv6:2601:184:c001:5cf7:a53f:baf7:aaf3:bce7? ([2601:184:c001:5cf7:a53f:baf7:aaf3:bce7])' + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(]', + '[\\]\\)]', + ) + const match = received_re.exec(received_header) + assert.equal(match[1], '2601:184:c001:5cf7:a53f:baf7:aaf3:bce7') + assert.equal(match.length, 2) + done() }) it('IPv6, Received header, bracketed, IPv6 prefix', function (done) { - const received_re = net_utils.get_ipany_re('^Received:.*?[\\[\\(](?:IPv6:)?', '[\\]\\)]'); - const match = received_re.exec('Received: from hub.freebsd.org (hub.freebsd.org [IPv6:2001:1900:2254:206c::16:88])'); - assert.equal(match[1], '2001:1900:2254:206c::16:88'); - assert.equal(match.length, 2); - done(); + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(](?:IPv6:)?', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from hub.freebsd.org (hub.freebsd.org [IPv6:2001:1900:2254:206c::16:88])', + ) + assert.equal(match[1], '2001:1900:2254:206c::16:88') + assert.equal(match.length, 2) + done() }) it('IPv6, folded Received header, bracketed, IPv6 prefix', function (done) { // note the use of [\s\S], '.' doesn't match newlines in JS regexp - const received_re = net_utils.get_ipany_re('^Received:[\\s\\S]*?[\\[\\(](?:IPv6:)?', '[\\]\\)]'); - const match = received_re.exec('Received: from freefall.freebsd.org (freefall.freebsd.org\r\n [IPv6:2001:1900:2254:206c::16:87])'); + const received_re = net_utils.get_ipany_re( + '^Received:[\\s\\S]*?[\\[\\(](?:IPv6:)?', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from freefall.freebsd.org (freefall.freebsd.org\r\n [IPv6:2001:1900:2254:206c::16:87])', + ) if (match) { - assert.equal(match[1], '2001:1900:2254:206c::16:87'); - assert.equal(match.length, 2); + assert.equal(match[1], '2001:1900:2254:206c::16:87') + assert.equal(match.length, 2) } - done(); + done() }) it('IPv6, Received header, bracketed, IPv6 prefix, localhost compressed', function (done) { - const received_re = net_utils.get_ipany_re('^Received:.*?[\\[\\(](?:IPv6:)?', '[\\]\\)]'); - const match = received_re.exec('Received: from ietfa.amsl.com (localhost [IPv6:::1])'); - assert.equal(match[1], '::1'); - assert.equal(match.length, 2); - done(); + const received_re = net_utils.get_ipany_re( + '^Received:.*?[\\[\\(](?:IPv6:)?', + '[\\]\\)]', + ) + const match = received_re.exec( + 'Received: from ietfa.amsl.com (localhost [IPv6:::1])', + ) + assert.equal(match[1], '::1') + assert.equal(match.length, 2) + done() }) it('IPv6 bogus', function (done) { - const is_bogus = net_utils.ipv6_bogus('::192.41.13.251'); // From https://github.com/haraka/Haraka/issues/2763 - assert.equal(is_bogus, true); - done(); + const is_bogus = net_utils.ipv6_bogus('::192.41.13.251') // From https://github.com/haraka/Haraka/issues/2763 + assert.equal(is_bogus, true) + done() }) }) @@ -1058,166 +1101,190 @@ describe('get_ips_by_host', function () { '192.48.85.147', '192.48.85.148', '192.48.85.149', - '2607:f060:b008:feed::2' + '2607:f060:b008:feed::2', ], - 'localhost.simerson.net': [ '127.0.0.1', '::1' ] + 'localhost.haraka.tnpi.net': ['127.0.0.1', '::1'], + // 'non-exist.haraka.tnpi.net': [], } for (const t in tests) { - it(`get_ips_by_host, ${t}`, function (done) { this.timeout(7000) net_utils.get_ips_by_host(t, function (err, res) { if (err && err.length) { - console.error(err); + console.error(err) + return done() } - assert.deepEqual(err, []); - assert.deepEqual(res.sort(), tests[t].sort()); - done(); - }); + assert.deepEqual(err, []) + assert.deepEqual(res.sort(), tests[t].sort()) + done() + }) }) it(`get_ips_by_host, promise, ${t}`, async function () { try { const res = await net_utils.get_ips_by_host(t) - assert.deepEqual(res.sort(), tests[t].sort()); - } - catch (e) { - console.error(e); + assert.deepEqual(res.sort(), tests[t].sort()) + } catch (e) { + console.error(e) } }) } }) -function _check_list (done, list, ip, res) { - assert.equal(net_utils.ip_in_list(list, ip), res); // keys of object - assert.equal(net_utils.ip_in_list(Object.keys(list), ip), res); // array - done(); +function _check_list(done, list, ip, res) { + assert.equal(net_utils.ip_in_list(list, ip), res) // keys of object + assert.equal(net_utils.ip_in_list(Object.keys(list), ip), res) // array + done() } describe('ip_in_list', function () { it('domain.com', function (done) { - _check_list(done, { 'domain.com': undefined }, 'domain.com', true); + _check_list(done, { 'domain.com': undefined }, 'domain.com', true) }) it('foo.com', function (done) { - _check_list(done, { }, 'foo.com', false); + _check_list(done, {}, 'foo.com', false) }) it('1.2.3.4', function (done) { - _check_list(done, { '1.2.3.4': undefined }, '1.2.3.4', true); + _check_list(done, { '1.2.3.4': undefined }, '1.2.3.4', true) }) it('1.2.3.4/32', function (done) { - _check_list(done, { '1.2.3.4/32': undefined }, '1.2.3.4', true); + _check_list(done, { '1.2.3.4/32': undefined }, '1.2.3.4', true) }) it('1.2.0.0/16 <-> 1.2.3.4', function (done) { - _check_list(done, { '1.2.0.0/16': undefined }, '1.2.3.4', true); + _check_list(done, { '1.2.0.0/16': undefined }, '1.2.3.4', true) }) it('1.2.0.0/16 <-> 5.6.7.8', function (done) { - _check_list(done, { '1.2.0.0/16': undefined }, '5.6.7.8', false); + _check_list(done, { '1.2.0.0/16': undefined }, '5.6.7.8', false) }) it('0000:0000:0000:0000:0000:0000:0000:0001', function (done) { - _check_list(done, { '0000:0000:0000:0000:0000:0000:0000:0001': undefined }, '0000:0000:0000:0000:0000:0000:0000:0001', true); + _check_list( + done, + { '0000:0000:0000:0000:0000:0000:0000:0001': undefined }, + '0000:0000:0000:0000:0000:0000:0000:0001', + true, + ) }) it('0:0:0:0:0:0:0:1', function (done) { - _check_list(done, { '0:0:0:0:0:0:0:1': undefined }, '0000:0000:0000:0000:0000:0000:0000:0001', true); + _check_list( + done, + { '0:0:0:0:0:0:0:1': undefined }, + '0000:0000:0000:0000:0000:0000:0000:0001', + true, + ) }) it('1.2 (bad config)', function (done) { - _check_list(done, { '1.2': undefined }, '1.2.3.4', false); + _check_list(done, { 1.2: undefined }, '1.2.3.4', false) }) it('1.2.3.4/ (mask ignored)', function (done) { - _check_list(done, { '1.2.3.4/': undefined }, '1.2.3.4', true); + _check_list(done, { '1.2.3.4/': undefined }, '1.2.3.4', true) }) it('1.2.3.4/gr (mask ignored)', function (done) { - _check_list(done, { '1.2.3.4/gr': undefined }, '1.2.3.4', true); + _check_list(done, { '1.2.3.4/gr': undefined }, '1.2.3.4', true) }) it('1.2.3.4/400 (mask read as 400 bits)', function (done) { - _check_list(done, { '1.2.3.4/400': undefined }, '1.2.3.4', true); + _check_list(done, { '1.2.3.4/400': undefined }, '1.2.3.4', true) }) }) describe('get_primary_host_name', function () { beforeEach(function (done) { - this.net_utils = require('../index'); - this.net_utils.config = this.net_utils.config.module_config(path.resolve('test')); - done(); + this.net_utils = require('../index') + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('test'), + ) + done() }) it('with me config', function (done) { - assert.equal(this.net_utils.get_primary_host_name(), 'test-hostname'); - done(); + assert.equal(this.net_utils.get_primary_host_name(), 'test-hostname') + done() }) it('without me config', function (done) { - this.net_utils.config = this.net_utils.config.module_config(path.resolve('doesnt-exist')); - assert.equal(this.net_utils.get_primary_host_name(), os.hostname()); - done(); + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('doesnt-exist'), + ) + assert.equal(this.net_utils.get_primary_host_name(), os.hostname()) + done() }) }) describe('on_local_interface', function () { beforeEach(function (done) { - this.net_utils = require('../index'); - this.net_utils.config = this.net_utils.config.module_config(path.resolve('test')); - done(); + this.net_utils = require('../index') + this.net_utils.config = this.net_utils.config.module_config( + path.resolve('test'), + ) + done() }) it('localhost 127.0.0.1', function (done) { - assert.equal(this.net_utils.on_local_interface('127.0.0.1'), true); - done(); + assert.equal(this.net_utils.on_local_interface('127.0.0.1'), true) + done() }) it('multicast 1.1.1.1', function (done) { - assert.equal(this.net_utils.on_local_interface('1.1.1.1'), false); - done(); + assert.equal(this.net_utils.on_local_interface('1.1.1.1'), false) + done() }) it('ipv6 localhost ::1', function (done) { - const r = this.net_utils.on_local_interface('::1'); + const r = this.net_utils.on_local_interface('::1') if (r) { - assert.equal(r, true); + assert.equal(r, true) } - done(); + done() }) }) describe('get_mx', function () { + this.timeout(12000) + beforeEach(function (done) { - this.net_utils = require('../index'); - done(); + this.net_utils = require('../index') + done() }) const validCases = { - 'tnpi.net' : 'mail.theartfarm.com', + 'tnpi.net': 'mail.theartfarm.com', 'matt@tnpi.net': 'mail.theartfarm.com', 'matt.simerson@gmail.com': /google.com/, - 'example.com' : '', + 'example.com': '', + 'no-mx.haraka.tnpi.net': '192.0.99.5', + 'bad-mx.haraka.tnpi.net': /99/, + 'über.haraka.tnpi.net': 'no-mx.haraka.tnpi.net', } - function checkValid (c, mxlist) { - if ('string' === typeof c) { - assert.equal(mxlist[0].exchange, c); - } - else { - assert.ok(c.test(mxlist[0].exchange)) + function checkValid(c, mxlist) { + try { + if ('string' === typeof c) { + assert.equal(mxlist[0].exchange, c) + } else { + assert.ok(c.test(mxlist[0].exchange)) + } + } catch (err) { + console.error(err) } } for (const c in validCases) { it(`gets MX records for ${c}`, function (done) { - this.timeout(7000) + this.timeout(12000) this.net_utils.get_mx(c, (err, mxlist) => { if (err) console.error(err) - assert.ifError(err); + assert.ifError(err) // assert.ok(mxlist.length); checkValid(validCases[c], mxlist) done() @@ -1225,7 +1292,7 @@ describe('get_mx', function () { }) it(`awaits MX records for ${c}`, async function () { - this.timeout(7000) + this.timeout(12000) const mxlist = await this.net_utils.get_mx(c) // assert.ok(mxlist.length); checkValid(validCases[c], mxlist) @@ -1234,15 +1301,14 @@ describe('get_mx', function () { // macOS: ENODATA, win: ENOTOUND, ubuntu: ESERVFAIL const invalidCases = { - 'invalid': /queryMx (ENODATA|ENOTFOUND|ESERVFAIL) invalid/, + invalid: /queryMx (ENODATA|ENOTFOUND|ESERVFAIL) invalid/, 'gmail.xn--com-0da': /(ENOTFOUND|Cannot convert name to ASCII)/, } - function checkInvalid (expected, actual) { + function checkInvalid(expected, actual) { if ('string' === typeof expected) { assert.strictEqual(actual, expected) - } - else { + } else { assert.equal(expected.test(actual), true) } } @@ -1250,7 +1316,6 @@ describe('get_mx', function () { for (const c in invalidCases) { it(`cb does not crash on invalid name: ${c}`, function () { this.net_utils.get_mx(c, (err, mxlist) => { - // console.error(err) assert.equal(mxlist.length, 0) checkInvalid(invalidCases[c], err.message) }) @@ -1260,11 +1325,67 @@ describe('get_mx', function () { try { const mxlist = await this.net_utils.get_mx(c) assert.equal(mxlist.length, 0) - } - catch (err) { - // console.error(err) + } catch (err) { checkInvalid(invalidCases[c], err.message) } }) } }) + +describe('resolve_mx_hosts', function () { + this.timeout(12000) + + beforeEach((done) => { + this.net_utils = require('../index') + done() + }) + + const expectedResolvedMx = [ + { + exchange: '2605:ae00:329::14', + priority: 10, + from_dns: 'mail.theartfarm.com', + }, + { + exchange: '66.128.51.165', + priority: 10, + from_dns: 'mail.theartfarm.com', + }, + ] + + it('resolves mx hosts to IPs, tnpi.net', async () => { + const r = await this.net_utils.resolve_mx_hosts([ + { exchange: 'mail.theartfarm.com', priority: 10, from_dns: 'tnpi.net' }, + ]) + assert.deepEqual(r, expectedResolvedMx) + }) + + it('resolves mx hosts to IPs, gmail.com', async () => { + const mxes = await this.net_utils.get_mx('gmail.com') + assert.equal(mxes.length, 5) + const r = await this.net_utils.resolve_mx_hosts(mxes) + assert.equal(r.length, 10) + }) + + it('returns IPs as is', async () => { + const r = await this.net_utils.resolve_mx_hosts(expectedResolvedMx) + assert.deepEqual(r, expectedResolvedMx) + }) + + it('returns sockets as-is', async () => { + const r = await this.net_utils.resolve_mx_hosts([{ path: '/var/run/sock' }]) + assert.deepEqual(r, [{ path: '/var/run/sock' }]) + }) + + it('resolve_mx_hosts, gmail.com', async () => { + const mxes = await this.net_utils.get_mx('gmail.com') + const r = await this.net_utils.resolve_mx_hosts(mxes) + assert.equal(r.length, 10) + }) + + it('resolve_mx_hosts, yahoo.com', async () => { + const mxes = await this.net_utils.get_mx('yahoo.com') + const r = await this.net_utils.resolve_mx_hosts([mxes[0]]) + assert.equal(r.length, 8) + }) +})