├── .eslintrc.json ├── .gitignore ├── test ├── update.js ├── example.js ├── extractIP_test │ ├── 1.txt │ └── 4.txt └── index.js ├── LICENSE.txt ├── package.json ├── README.md └── lib └── whois.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "google", 3 | "rules": { 4 | "indent": ["error", 4], 5 | "key-spacing": ["error", { "align": "colon" }], 6 | "no-multi-spaces": ["error", { "exceptions": { "VariableDeclarator": true } }], 7 | "camelcase": ["off"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | token.json 3 | GeoLite2-ASN.mmdb 4 | GeoLite2-City.mmdb 5 | GeoLite2-ASN.tar.gz 6 | GeoLite2-City.tar.gz 7 | IP2LOCATION-LITE-ASN.zip 8 | IP2LOCATION-LITE-DB5.IPV6.BIN 9 | IP2LOCATION-LITE-DB5.IPV6.zip 10 | IP2LOCATION-LITE-DB5.IPV6.ZIP 11 | IP2PROXY-LITE-PX4.BIN 12 | IP2PROXY-LITE-PX4.BIN.ZIP 13 | -------------------------------------------------------------------------------- /test/update.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path'); 4 | 5 | 6 | // require lib 7 | const whois = require('./../lib/whois'); 8 | 9 | const local_path = path.join(__dirname, 'GeoIP'); 10 | const token = require('./token.json'); 11 | 12 | whois.geoUpdate(local_path, token) 13 | .then(() => { 14 | console.log('OK'); 15 | console.log(); 16 | }) 17 | .catch(err => { 18 | console.log('ERROR'); 19 | console.log(err); 20 | }); 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alexander Zubakov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-xwhois", 3 | "version": "2.0.10", 4 | "description": "Advanced whois library with extended capabilities.", 5 | "main": "./lib/whois.js", 6 | "author": "Alexander Russkiy ", 7 | "license": "MIT", 8 | "scripts": { 9 | "test": "mocha test/index.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/kolonist/node-xwhois" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/kolonist/node-xwhois/issues" 17 | }, 18 | "keywords": [ 19 | "whois", 20 | "geoIP", 21 | "geolocation", 22 | "geo location", 23 | "BGP", 24 | "AS", 25 | "Autonomous system", 26 | "domain name", 27 | "DNS", 28 | "IPv4", 29 | "IPv6" 30 | ], 31 | "dependencies": { 32 | "@maxmind/geoip2-node": "latest", 33 | "ip2location-nodejs": "latest", 34 | "ip2proxy-nodejs": "latest", 35 | "jszip": "latest", 36 | "tar-stream": "latest", 37 | "whois": "latest" 38 | }, 39 | "devDependencies": { 40 | "eslint": "latest", 41 | "eslint-config-google": "latest", 42 | "mocha": "latest" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | //const whois = require('node-xwhois'); 3 | const whois = require('./../lib/whois'); 4 | 5 | const host1 = 'xinit.co'; 6 | const host2 = '8.8.8.8'; 7 | const host3 = '199.87.154.255'; 8 | const ipStr = ` 9 | test raw text test raw text 10 | 77.109.141.140 (37.187.130.68) ++ 188.40.143.7 $$ 95.174.227.96 test raw text 11 | test raw text test raw text test raw text test raw text 12 | 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d test raw text test raw text 13 | 2001:0db8:0000:0000:0000:0000:ae21:ad12 14 | test raw text 15 | 188.40.143.7 188.40.143.7 16 | `; 17 | 18 | 19 | whois.reverse(host2) 20 | .then(hostnames => console.log(`${host2} reverse:\n`, JSON.stringify(hostnames, null, 4))) 21 | .catch(err => console.log(err)); 22 | 23 | 24 | whois.nslookup(host1) 25 | .then(info => console.log(`${host1} nslookup:\n`, JSON.stringify(info, null, 4))) 26 | .catch(err => console.log(err)); 27 | 28 | 29 | whois.whois(host1) 30 | .then(info => console.log(`${host1} whois:\n`, info)) 31 | .catch(err => console.log(err)); 32 | 33 | 34 | whois.torInfo(host3) 35 | .then(info => console.log(`${host3} torInfo:\n`, info)) 36 | .catch(err => console.log(err)); 37 | 38 | 39 | whois.extractIP(ipStr) 40 | .then(info => console.log('extractIP:\n', JSON.stringify(info, null, 4))) 41 | .catch(err => console.log(err)); 42 | 43 | 44 | const extractIPGen = whois.extractIPGen(ipStr); 45 | let ip; 46 | while (undefined !== (ip = extractIPGen.next().value)) 47 | console.log('extractIPGen:', ip); 48 | 49 | 50 | whois.bgpInfo(host3) 51 | .then(info => console.log(`${host3} bgpInfo:\n`, JSON.stringify(info, null, 4))) 52 | .catch(err => console.log(err)); 53 | 54 | 55 | whois.geoInit('test/GeoIP', { 56 | ip2location: { 57 | db : 'ip2location.bin', 58 | source: ['IP2LOCATION-LITE-DB5.IPV6.BIN', 'IP2LOCATION-LITE-DB5.BIN'] 59 | }, 60 | maxMind : {city: 'GeoLiteCity.dat', org: 'GeoIPASNum.dat' }, 61 | maxMindv6: {city: 'GeoLiteCityv6.dat', org: 'GeoIPASNumv6.dat'}, 62 | maxMind2 : 'GeoLite2-City.mmdb' 63 | }) 64 | .then(() => { 65 | whois.geoInfo(host3) 66 | .then(info => console.log(`${host3} geoInfo:\n`, info)) 67 | .catch(err => console.log(err)); 68 | 69 | whois.hostInfo(host1) 70 | .then(data => console.log(`${host1} info:\n`, JSON.stringify(data, null, 4))) 71 | .catch(err => console.log(err)); 72 | 73 | whois.hostInfo(host2) 74 | .then(data => console.log(`${host2} info:\n`, JSON.stringify(data, null, 4))) 75 | .catch(err => console.log(err)); 76 | }) 77 | .catch(err => console.log(err)); 78 | -------------------------------------------------------------------------------- /test/extractIP_test/1.txt: -------------------------------------------------------------------------------- 1 | 2 | Hide My Ass 3 | 4 | Home How it works Pricing Tools & Contact Help 5 | 6 | Free IP:port proxy lists 7 | 8 | We offer the largest real-time database of free working public proxies. Check it out below – or try our Premium service. For a one-off registration fee, we’ll send the entire list to your inbox every day in raw .txt format. 9 | Register here for our Premium service 10 | 11 | Our Premium service emails you a daily attachment of our entire public proxy database in a raw .txt format, making downloading and using public proxy servers much easier. Only $24.95 for life! 12 | 13 | Use a proxy server to mask your true IP address and surf anonymously online 14 | Public proxy servers can be used with web browsers and other apps that support proxy use 15 | Our proxy lists are checked in real time with auto-updatable results 24/7 16 | 17 | Sortable by PlanetLab/Codden, so Planet Lab proxies can be easily distinguished 18 | Sortable by speed, with the fastest proxies listed first 19 | Sortable by country 20 | 21 | Free IP:PORT proxy lists 22 | Proxy country 23 | All countries 24 | 25 | Sort by count 26 | Sort by name 27 | 28 | Ports 29 | All ports 30 | TIP: Enter specific ports in the above box. Separate more than one port by a comma (8080, 80,443 ...). A maximum of 20 ports allowed. 31 | Protocol 32 | HTTP 33 | HTTPS 34 | socks4/5 35 | Anonymity level 36 | None 37 | Low 38 | Medium 39 | High 40 | High +KA 41 | PlanetLab 42 | Include 43 | Speed 44 | Slow 45 | Medium 46 | Fast 47 | Connection Time 48 | Slow 49 | Medium 50 | Fast 51 | Last Update IP Address Port Country Speed Connection Time Type Anon 52 | 48secs 106.58.63.63 55336 flag China 53 | 54 | HTTPS High +KA 55 | 48secs 60.164.223.11 55336 flag China 56 | 57 | HTTP Low 58 | 48secs 218.61.39.44 55336 flag China 59 | 60 | HTTPS High +KA 61 | 48secs 190.228.71.113 80 flag Argentina 62 | 63 | HTTPS High +KA 64 | 48secs 157.122.96.65 55336 flag China 65 | 66 | HTTP Low 67 | 48secs 46.0.195.185 3128 flag Russia 68 | 69 | HTTP Low 70 | 48secs 175.43.123.14 55336 flag China 71 | 72 | HTTPS High +KA 73 | 1min 116.31.102.82 80 flag Japan 74 | 75 | HTTPS High +KA 76 | 1min 123.58.129.48 443 flag China 77 | 78 | HTTPS High +KA 79 | 1min 112.114.63.20 55336 flag China 80 | 81 | HTTPS High +KA 82 | 1min 31.146.182.122 443 flag Georgia 83 | 84 | HTTPS High +KA 85 | 1min 220.181.143.14 9999 flag China 86 | 87 | HTTP Low 88 | 2mins 111.12.13.164 55336 flag China 89 | 90 | HTTP Low 91 | 2mins 112.114.63.36 55336 flag China 92 | 93 | HTTPS High +KA 94 | 2mins 222.45.16.207 8118 flag China 95 | 96 | HTTP High +KA 97 | 2mins 115.238.225.26 80 flag China 98 | 99 | HTTP Low 100 | 2mins 218.61.39.56 55336 flag China 101 | 102 | HTTPS High +KA 103 | 2mins 183.207.229.200 8080 flag China 104 | 105 | HTTP High +KA 106 | 2mins 218.60.101.25 55336 flag China 107 | 108 | HTTPS High +KA 109 | 3mins 42.96.175.11 3128 flag China 110 | 111 | HTTPS High +KA 112 | 3mins 111.161.126.100 80 flag China 113 | 114 | HTTPS High +KA 115 | 3mins 221.181.39.139 55336 flag China 116 | 117 | HTTPS High +KA 118 | 3mins 211.23.19.130 80 flag Taiwan 119 | 120 | HTTP Low 121 | 3mins 218.206.207.37 55336 flag China 122 | 123 | HTTPS High +KA 124 | 4mins 146.255.78.254 8080 flag Macedonia 125 | 126 | HTTP Low 127 | 4mins 81.30.71.165 80 flag Netherlands 128 | 129 | HTTP High +KA 130 | 4mins 124.200.39.254 8118 flag China 131 | 132 | HTTP High +KA 133 | 4mins 61.19.86.244 808 flag Thailand 134 | 135 | HTTPS High +KA 136 | 5mins 112.114.63.35 55336 flag China 137 | 138 | HTTPS High +KA 139 | 5mins 124.65.163.10 8080 flag China 140 | 141 | HTTP High +KA 142 | 5mins 183.87.117.253 80 flag India 143 | 144 | HTTP Low 145 | 5mins 31.170.178.38 8080 flag Czechia 146 | 147 | HTTPS High +KA 148 | 5mins 220.168.133.14 55336 flag China 149 | 150 | HTTP Low 151 | 5mins 106.58.63.84 55336 flag China 152 | 153 | HTTPS High +KA 154 | 6mins 52.8.111.148 3128 flag USA 155 | 156 | HTTPS High +KA 157 | 6mins 174.46.79.26 80 flag USA 158 | 159 | HTTP High +KA 160 | 6mins 124.202.218.218 8118 flag China 161 | 162 | HTTP High +KA 163 | 6mins 101.71.27.120 80 flag China 164 | 165 | HTTP High +KA 166 | 6mins 69.57.48.211 8080 flag USA 167 | 168 | HTTP Low 169 | 6mins 111.161.126.98 80 flag China 170 | 171 | HTTPS High +KA 172 | 6mins 112.114.63.12 55336 flag China 173 | 174 | HTTPS High +KA 175 | 6mins 185.28.193.95 8080 flag Europe 176 | 177 | HTTPS High +KA 178 | 6mins 60.164.223.7 55336 flag China 179 | 180 | HTTP Low 181 | 6mins 186.15.83.185 8080 flag Costa Rica 182 | 183 | HTTP Low 184 | 7mins 112.25.8.137 55336 flag China 185 | 186 | HTTPS High +KA 187 | 7mins 178.215.111.71 9999 flag Russia 188 | 189 | HTTP Low 190 | 7mins 183.250.177.17 55336 flag China 191 | 192 | HTTP Low 193 | 7mins 122.225.106.36 80 flag China 194 | 195 | HTTP Low 196 | 8mins 175.43.123.28 55336 flag China 197 | 198 | HTTPS High +KA 199 | 8mins 115.231.97.251 3128 flag China 200 | 201 | HTTPS High +KA 202 | 203 | Privacy Terms & Conditions 204 | 205 | Copyright ©2005-2014 Privax Ltd. All Rights Reserved. 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Library can get various network information about domain names and IP-addresses. 4 | 5 | Currently it provides the following information: 6 | - whois (currently using [node-whois](https://github.com/hjr265/node-whois) library) 7 | - BGP information (AS number and name) 8 | - define whether IP address is TOR node either exit node, or entry node, or other 9 | - DNS information 10 | - GeoLocation information simultaneously using MaxMind and IP2Location databases 11 | 12 | # Installation 13 | 14 | You can install it with this command: 15 | ```bash 16 | npm install node-xwhois 17 | ``` 18 | 19 | 20 | # Usage 21 | 22 | Simplest way to get all possible information about domain name or IP address is using `hostInfo()` function: 23 | 24 | ```JavaScript 25 | 'use strict' 26 | const whois = require('node-xwhois'); 27 | 28 | const host1 = 'xinit.ru'; 29 | const host2 = '8.8.8.8'; 30 | 31 | whois.geoInit('test/GeoIP') 32 | .then(() => { 33 | whois.hostInfo(host1) 34 | .then(data => console.log(`${host1} info:\n`, JSON.stringify(data, null, 4))) 35 | .catch(err => console.log(err)); 36 | 37 | whois.hostInfo(host2) 38 | .then(data => console.log(`${host2} info:\n`, JSON.stringify(data, null, 4))) 39 | .catch(err => console.log(err)); 40 | }) 41 | .catch(err => console.log(err)); 42 | ``` 43 | 44 | All asynchronous functions in this library return Promises. 45 | 46 | 47 | # Documentation 48 | - [ip2long](#ip2longip) 49 | - [isIP](#isiphost) 50 | - [isDomain](#isdomainhost) 51 | - [reverse](#reverseip) 52 | - [nslookup](#nslookuphost) 53 | - [whois](#whoishost) 54 | - [torInfo](#torinfoip) 55 | - [extractIP](#extractipstr) 56 | - [geoInit](#geoinitdbpath) 57 | - [geoInfo](#geoinfohost) 58 | - [geoUpdate](#geoupdatedbpath-token) 59 | - [bgpInfo](#bgpinfohost) 60 | - [info](#infohost) 61 | 62 | ## `ip2long(ip)` 63 | A JavaScript equivalent of PHP's ip2long(). Convert IPv4 address in dotted notation to 32-bit long integer. 64 | You can pass IP in all possible representations, i.e.: 65 | ``` 66 | 192.0.34.166 67 | 0xC0.0x00.0x02.0xEB 68 | 0xC00002EB 69 | 3221226219 70 | 0.0xABCDEF 71 | 255.255.255.256 72 | 0300.0000.0002.0353 73 | 030000001353 74 | ``` 75 | ### Parameters 76 | **ip** 77 | String. IPv4-address in one of possible representations. 78 | 79 | ### Return 80 | 32-bit number notation of IP-address expressed in decimal. 81 | 82 | ## `isIP(host)` 83 | Detect if `host` is correct IP-address. Internally uses `net.isIP()`. 84 | 85 | ### Parameters 86 | **host** 87 | String to test. 88 | 89 | ### Return 90 | `true` if `host` is correct IP address or `false` otherwise. 91 | 92 | ## `isDomain(host)` 93 | Detect if host is correct domain name. It can't test IDN's. And it can't define if domain name is really exist or can exist. This function just performs syntax check. 94 | 95 | ### Parameters 96 | **host** 97 | String to test. 98 | 99 | ### Return 100 | True if `host` is correct domain name false otherwise. 101 | 102 | ## `reverse(ip)` 103 | Define domain names by IP-address using reverse domain request. 104 | 105 | ### Parameters 106 | **ip** 107 | IP-address to reverse. 108 | 109 | ### Return 110 | Promise where `then()` takes function with array of hostnames. 111 | 112 | ### Example 113 | ```JavaScript 114 | const host = '8.8.8.8'; 115 | 116 | whois.reverse(host) 117 | .then(hostnames => console.log(`${host} reverse:\n`, JSON.stringify(hostnames, null, 4))) 118 | .catch(err => console.log(err)); 119 | ``` 120 | 121 | ## `nslookup(host)` 122 | Get host info of domain name like `host -a` command. 123 | 124 | ### Parameters 125 | **host** 126 | Domain name. 127 | 128 | ### Return 129 | Promise where `then()` takes function with object like this: 130 | ```JavaScript 131 | { 132 | 'A' : ['IPv4-addresses'], 133 | 'AAAA' : ['IPv6-addresses'], 134 | 'MX' : ['MX-records' ], 135 | 'TXT' : ['TXT-records' ], 136 | 'SRV' : ['SRV-records' ], 137 | 'NS' : ['NS-records' ], 138 | 'CNAME': ['CNAME-records' ], 139 | 'SOA' : ['SOA-records' ] 140 | } 141 | ``` 142 | 143 | ### Example 144 | ```JavaScript 145 | const host = 'xinit.co'; 146 | 147 | whois.nslookup(host) 148 | .then(info => console.log(`${host} nslookup:\n`, JSON.stringify(info, null, 4))) 149 | .catch(err => console.log(err)); 150 | ``` 151 | 152 | ## `whois(host)` 153 | Perform whois request. 154 | 155 | ### Parameters 156 | **host** 157 | Domain name or IP-address. 158 | 159 | ### Return 160 | Promise where `then()` takes function with whois text. 161 | 162 | ### Example 163 | ```JavaScript 164 | const host = 'xinit.co'; 165 | 166 | whois.whois(host) 167 | .then(info => console.log(`${host} whois:\n`, JSON.stringify(info, null, 4))) 168 | .catch(err => console.log(err)); 169 | ``` 170 | 171 | ## `torInfo(ip)` 172 | Check if IP address is a TOR node. 173 | 174 | ### Parameters 175 | **ip** 176 | IP-address. 177 | 178 | ### Return 179 | Promise where `then()` takes function with object like this: 180 | ```JavaScript 181 | { 182 | 'nodename': 'Name of TOR node', 183 | 'port' : [0, 0], // port numbers of TOR node 184 | 'exitNode': true // if true then this is exit node 185 | } 186 | ``` 187 | If IP does not belong to TOR node then null will be passed instead of described object. 188 | 189 | ### Example 190 | ```JavaScript 191 | const host = '199.87.154.255'; 192 | 193 | whois.torInfo(host) 194 | .then(info => console.log(`${host} torInfo:\n`, JSON.stringify(info, null, 4))) 195 | .catch(err => console.log(err)); 196 | ``` 197 | 198 | ## `extractIP(str)` 199 | Extract IP-addresses from raw text. If some IP-address appears in `str` multiple times then it will be returned in answer only once. 200 | 201 | ### Parameters 202 | **str** 203 | String to extract IP-addresses from. 204 | 205 | ### Return 206 | Promise where `then()` takes function with array of IP-addresses as strings. 207 | 208 | ### Example 209 | ```JavaScript 210 | const ipStr = ` 211 | test raw text test raw text 212 | 77.109.141.140 (37.187.130.68) ++ 188.40.143.7 $$ 95.174.227.96 test raw text 213 | test raw text test raw text test raw text test raw text 214 | 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d test raw text test raw text 215 | 2001:0db8:0000:0000:0000:0000:ae21:ad12 216 | test raw text 217 | 188.40.143.7 188.40.143.7 218 | `; 219 | 220 | whois.extractIP(ipStr) 221 | .then(info => console.log('extractIP:\n', JSON.stringify(info, null, 4))) 222 | .catch(err => console.log(err)); 223 | ``` 224 | 225 | ## `geoInit(dbPath)` 226 | Initialize script to get GeoLocation information. You need to call this function before using `geoInfo()` or `hostInfo()`. 227 | 228 | ### Parameters 229 | **dbPath** 230 | Path to directory where GeoLocation DB-files located. 231 | 232 | You need to download GeoLocation databases by yourself or using `geoUpdate()`. 233 | 234 | If you will download it manually you should get following files:
235 | `GeoLite2-City.mmdb` and `GeoLite2-ASN.mmdb` from 236 | [MaxMind](https://www.maxmind.com/en/geoip2-databases)
237 | `IP2LOCATION-LITE-DB5.IPV6.BIN` and `IP2PROXY-LITE-PX4.BIN` from 238 | [IP2Location](https://www.ip2location.com/database) 239 | 240 | ### Return 241 | Promise without any parameters. Run `geoInfo()` only within `then()` of this 242 | Promise to be sure that all GeoLocation DB properly loaded. 243 | 244 | 245 | ## `geoInfo(host)` 246 | Get GeoLocation information. 247 | 248 | ### Parameters 249 | **host** 250 | IP address to get info about. 251 | 252 | ### Return 253 | Promise where `then()` has object as parameter like in this example: 254 | ```JavaScript 255 | { 256 | ip : '78.46.112.219', 257 | asn : '24940', 258 | as_org : 'Hetzner Online GmbH', 259 | proxy : 'VPN', // see https://www.ip2proxy.com/ for possible values 260 | country_code: ['DE'], 261 | country : ['Germany'], 262 | region : [ 'Bayern', 'Bavaria', 'Sachsen' ], 263 | city : [ 'Nürnberg', 'Nuremberg', 'Falkenstein' ], 264 | country_ru : 'Германия', 265 | region_ru : 'Бавария', 266 | city_ru : 'Нюрнберг', 267 | timezone : 'Europe/Berlin', 268 | coords : [ 269 | { lat: 49.4478, lon: 11.068299999999994 }, 270 | { lat: 49.4478, lon: 11.0683 } 271 | ] 272 | } 273 | ``` 274 | 275 | ### Example 276 | ```JavaScript 277 | const host = '199.87.154.255'; 278 | 279 | whois.geoInit('test/GeoIP') 280 | .then(() => { 281 | return whois.geoInfo(host); 282 | }) 283 | .then(info => { 284 | console.log(`${host} geoInfo:\n`, info) 285 | }) 286 | .catch(err => console.log(err)); 287 | ``` 288 | 289 | ## `geoUpdate(dbPath, token)` 290 | Update MaxMind and IP2Location databases. 291 | 292 | ### Parameters 293 | **dbPath** 294 | Full local path to store DB files. 295 | 296 | **token** 297 | API token to download IP2Location database. You should register on https://www.ip2location.com/ to get it. 298 | 299 | ### Return 300 | Promise without any parameters. 301 | 302 | ### Example 303 | ```JavaScript 304 | const path = './GeoIP'; 305 | const token = 'insert your token here'; 306 | 307 | whois.geoUpdate(path, token) 308 | .then(() => { 309 | console.log('OK'); 310 | }) 311 | .catch(err => { 312 | console.log('ERROR'); 313 | console.log(err); 314 | }); 315 | ``` 316 | 317 | 318 | ## `bgpInfo(host)` 319 | Get BGP information, such as Autonomous System number. You can get this info manually by using this command in Linux console: 320 | ```bash 321 | $ echo "-c -r -a -u -p 109.111.139.45" | nc whois.cymru.com 43 322 | ``` 323 | 324 | ### Parameters 325 | **host** 326 | IP address to get info about. 327 | 328 | ### Return 329 | Promise with array of the following objects in `then()`: 330 | ```JavaScript 331 | [{ 332 | "as": "18451", 333 | "ip": "199.87.154.255", 334 | "prefix": "199.87.152.0/21", 335 | "country_code": "CA", 336 | "registry": "arin", 337 | "allocation_date": "2011-01-31", 338 | "name": "ASN-LES - LES.NET,CA" 339 | }] 340 | ``` 341 | 342 | ### Example 343 | ```JavaScript 344 | const host = '199.87.154.255'; 345 | 346 | whois.bgpInfo(host) 347 | .then(info => console.log(`${host} bgpInfo:\n`, JSON.stringify(info, null, 4))) 348 | .catch(err => console.log(err)); 349 | ``` 350 | 351 | ## `info(host)` 352 | Get all possible information about domain name or IP-address. 353 | 354 | ### Parameters 355 | **host** 356 | Domain name or IP-address. 357 | 358 | ### Return 359 | Promise where `then()` has the following object as parameter: 360 | ```JavaScript 361 | { 362 | host : host, 363 | isIP : true, 364 | longIP : null, 365 | reverse : null, 366 | geoInfo : null, 367 | torInfo : null, 368 | bgpInfo : null, 369 | isDomain: false, 370 | nslookup: null, 371 | whois : null 372 | } 373 | ``` 374 | 375 | ### Example 376 | ```JavaScript 377 | const host1 = 'xinit.co'; 378 | const host2 = '8.8.8.8'; 379 | 380 | whois.geoInit('test/GeoIP') 381 | .then(() => { 382 | whois.info(host1) 383 | .then(data => console.log(`${host1} info:\n`, JSON.stringify(data, null, 4))) 384 | .catch(err => console.log(err)); 385 | 386 | whois.info(host2) 387 | .then(data => console.log(`${host2} info:\n`, JSON.stringify(data, null, 4))) 388 | .catch(err => console.log(err)); 389 | }) 390 | .catch(err => console.log(err)); 391 | ``` 392 | 393 | 394 | *** 395 | 396 | @license MIT
397 | @version 2.0.10
398 | @author Alexander Russkiy 399 | -------------------------------------------------------------------------------- /test/extractIP_test/4.txt: -------------------------------------------------------------------------------- 1 | 2 | Manual:IPv6/Address 3 | < Manual:IPv6 4 | Version.png 5 | 6 | Applies to RouterOS: v3, v4 + 7 | Contents 8 | 9 | 1 Summary 10 | 2 Address Expression 11 | 2.1 Prefix 12 | 3 Address Types 13 | 3.1 Unicast Addresses 14 | 3.1.1 Link-local address 15 | 3.1.2 Special purpose address 16 | 3.1.3 Compatibility address 17 | 3.2 Multicast address 18 | 3.3 Anycast address 19 | 4 Interface Identifier 20 | 4.1 EUI-64 21 | 5 Properties 22 | 6 Examples 23 | 6.1 Manual address configuration 24 | 25 | Summary 26 | 27 | Sub-menu: /ipv6 address 28 | Standards: RFC 4291 29 | 30 | 31 | IPv6 uses 16 bytes addresses compared to 4 byte addresses in IPv4. IPv6 address syntax and types are described in RFC 4291. 32 | 33 | There are multiple IPv6 address types, that can be recognized by their prefix. RouterOS distinguishes the following: 34 | 35 | multicast (with prefix ff00::/8) 36 | link-local (with prefix fe80::/10) 37 | loopback (the address ::1/128) 38 | unspecified (the address ::/128) 39 | other (all other addresses, including the obsoleted site-local addresses, and RFC 4193 unique local addresses; they all are treated as global unicast). 40 | 41 | One difference between IPv6 and IPv4 addressing is that IPv6 automatically generates a link-local IPv6 address for each active interface that has IPv6 support. 42 | Address Expression 43 | 44 | IPv6 addresses are represented a little bit different than IPv4 addresses. For IPv6, the 128-bit address is divided in eight 16-bit blocks, and each 16-bit block is converted to a 4-digit hexadecimal number and separated by colons. The resulting representation is called colon-hexadecimal. 45 | 46 | In example above IPv6 address in binary format is converted to colon-hexadecimal representation 47 | 48 | 0010000000000001 0000010001110000 0001111100001001 0000000100110001 49 | 0000000000000000 0000000000000000 0000000000000000 0000000000001001 50 | 51 | 2001:0470:1f09:0131:0000:0000:0000:0009 52 | 53 | 54 | IPv6 address can be further simplified by removing leading zeros in each block: 55 | 56 | 2001:470:1f09:131:0:0:0:9 57 | 58 | 59 | As you can see IPv6 addresses can have long sequences of zeros. These contiguous sequence can be compressed to :: 60 | 61 | 2001:470:1f09:131::9 62 | 63 | 64 | Icon-note.png 65 | 66 | Note: Zero compression can only be used once. Otherwise, you could not determine the number of 0 bits represented by each instance of a double-colon 67 | 68 | 69 | Prefix 70 | 71 | IPv6 prefix is written in address/prefix-length format. Compared to IPv4 decimal representation of network mask cannot be used. Prefix examples: 72 | 73 | 2001:470:1f09:131::/64 74 | 2001:db8:1234::/48 75 | 2607:f580::/32 76 | 2000::/3 77 | 78 | Address Types 79 | 80 | Several IPv6 address types exist: 81 | 82 | Unicast 83 | Anycast 84 | Multicast 85 | 86 | As you can see there are no Broadcast addresses in ipv6 network, compared to IPv4 broadcast functionality was completely replaced with multicast. 87 | Unicast Addresses 88 | 89 | Packets addressed to a unicast address are delivered only to a single interface. To this group belong: 90 | 91 | globally unique addresses and can be used to connect to addresses with global scope anywhere. 92 | link-local addresses 93 | site-local addresses (FEC0::/48) - deprecated 94 | special purpose addresses 95 | compatibility addresses 96 | 97 | Global unicast address can be automatically assigned to the node by Stateless Address auto-configuration. Read More >>. 98 | Link-local address 99 | 100 | A link-local address is required on every IPv6-enabled interface, applications may rely on the existence of a link-local address even when there is no IPv6 routing, that is why link-local address is generated automatically for every active interface using it's interface identifier (calculated EUI-64 from MAC address if present). Address prefix is always FE80::/64 and IPv6 router never forwards link-local traffic beyond the link. 101 | 102 | These addresses are comparable to the auto-configuration addresses 169.254.0.0/16 of IPv4. 103 | 104 | A link-local address is also required for Neighbor Discovery processes. 105 | Icon-note.png 106 | 107 | Note: If interface is set as bridge port, interface specific link-local address is removed leaving only bridge link-local address 108 | 109 | 110 | Special purpose address 111 | Address Description 112 | Unspecified address (::/128) Never assigned to an interface or used as a destination address, used only to indicate the absence of an address. Equivalent to IPv4 0.0.0.0 address. 113 | loopback address (::1/128) Used to identify a loopback interface, enabling a node to send packets to itself. It is equivalent to the IPv4 loopback address of 127.0.0.1. 114 | Compatibility address 115 | Address Description 116 | IPv4 compatible address used by dual-stack nodes that are communicating with IPv6 over an IPv4 infrastructure. When the IPv4-compatible address is used as an IPv6 destination, IPv6 traffic is automatically encapsulated with an IPv4 header and sent to the destination by using the IPv4 infrastructure. Address is written in following format ::w.x.y.z, where w.x.y.z is the dotted decimal representation of a public IPv4 address. 117 | IPv4 mapped address used to represent an IPv4-only node to an IPv6 node. It is used only for internal representation. The IPv4-mapped address is never used as a source or destination address for an IPv6 packet. The IPv6 protocol does not support the use of IPv4-mapped addresses. Address is written in following format: ::ffff:w.x.y.z, where w.x.y.z is the dotted decimal representation of a public IPv4 address. 118 | 2002::/16 this prefix is used for 6to4 addressing. Here, an address from the IPv4 network 192.88.99.0/24 is also used. 119 | Multicast address 120 | 121 | Most important multicast aspects are: 122 | 123 | traffic is sent to a single address but is processed by multiple hosts; 124 | group membership is dynamic, allowing hosts to join and leave the group at any time; 125 | in IPv6, Multicast Listener Discovery (MLD) messages are used to determine group membership on a network segment, also known as a link or subnet; 126 | host can send traffic to the group's address without belonging to the corresponding group. 127 | 128 | A single IPv6 multicast address identifies each multicast group. Each group's reserved IPv6 address is shared by all host members of the group who listen and receive any IPv6 messages sent to the group's address. 129 | 130 | Multicast address consists of the following parts: [1] 131 | 132 | The first 8 bits in multicast address is always 1111 1111 (which is FF in hexadecimal format). 133 | Flag uses the 9th to 12th bit and shows if this multicast address is predefined (well-known) or not. If it is well-known, all bits are 0s. 134 | Scope ID indicates to which scope multicast address belongs, for example, Scope ID=2 is link-local scope. 135 | Group ID is used to specify a multicast group. There are predefined group IDs, such as Group ID=1 - all nodes. Therefore, if multicast address is ff02::1, that means Scope ID=2 and Group ID=1, indicating all nodes in link-local scope. This is analogous to broadcast in IPv4. 136 | 137 | Here is the table of reserved IPV6 addresses for multicasting: 138 | Address Description 139 | FF02::1 The all-nodes address used to reach all nodes on the same link. 140 | FF02::2 The all-routers address used to reach all routers on the same link. 141 | FF02::5 The all-Open Shortest Path First (OSPF) routers address used to reach all OSPF routers on the same link. 142 | FF02::6 The all-OSPF designated routers address used to reach all OSPF designated routers on the same link. 143 | FF02::1:FFXX:XXXX The solicited-node address used in the address resolution process to resolve the IPv6 address of a link-local node to its link-layer address. The last 24 bits (XX:XXXX) of the solicited-node address are the last 24 bits of an IPv6 unicast address. 144 | 145 | The following table is a partial list of IPv6 multicast addresses that are reserved for IPv6 multicasting and registered with the Internet Assigned Numbers Authority (IANA). For complete list of assigned addresses read IANA document. 146 | 147 | Multicast addresses can be used to discover nodes in a network. For example, discover all nodes 148 | 149 | mrz@bumba:/media/aaa/ver$ ping6 ff02::1%eth0 150 | PING ff02::1%eth0(ff02::1) 56 data bytes 151 | 64 bytes from fe80::21a:4dff:fe5d:8e56: icmp_seq=1 ttl=64 time=0.037 ms 152 | 64 bytes from fe80::20c:42ff:fe0d:2c38: icmp_seq=1 ttl=64 time=4.03 ms (DUP!) 153 | 64 bytes from fe80::20c:42ff:fe28:7945: icmp_seq=1 ttl=64 time=5.59 ms (DUP!) 154 | 64 bytes from fe80::20c:42ff:fe49:fce5: icmp_seq=1 ttl=64 time=5.60 ms (DUP!) 155 | 64 bytes from fe80::20c:42ff:fe21:f1ec: icmp_seq=1 ttl=64 time=5.88 ms (DUP!) 156 | 64 bytes from fe80::20c:42ff:fe72:a1b0: icmp_seq=1 ttl=64 time=6.70 ms (DUP!) 157 | 158 | discover all routers 159 | 160 | mrz@bumba:/media/aaa/ver$ ping6 ff02::2%eth0 161 | PING ff02::2%eth0(ff02::2) 56 data bytes 162 | 64 bytes from fe80::20c:42ff:fe28:7945: icmp_seq=1 ttl=64 time=0.672 ms 163 | 64 bytes from fe80::20c:42ff:fe0d:2c38: icmp_seq=1 ttl=64 time=1.44 ms (DUP!) 164 | 165 | Anycast address 166 | 167 | Anycast address is a new type of address incorporated in IPv6. 168 | 169 | Anycasting is a new networking paradigm supporting service–oriented Addresses where an identical address can be assigned to multiple nodes providing a specific service. An anycast packet (i.e., one with an anycast destination address) is delivered to one of these nodes with the same anycast address. 170 | 171 | Anycast address is not assigned a specific address range. It is assigned from unicast address range. 172 | Interface Identifier 173 | 174 | The last 64 bits of an IPv6 address are the interface identifier that is unique to the 64-bit prefix of the IPv6 address. There are several ways how to determine interface identifier: 175 | 176 | EUI-64; 177 | randomly generated to provide a level of anonymity; 178 | manually configured. 179 | 180 | EUI-64 181 | 182 | Traditional interface identifiers for network adapters are 48-bit MAC address. This address consists of a 24-bit manufacturer ID and a 24-bit board ID. 183 | 184 | IEEE EUI-64 is a new standard for network interface addressing. The company ID is still 24-bits in length, but the extension ID is 40 bits, creating a much larger address space for a network adapters. 185 | 186 | To create an EUI-64 address from the interface MAC address: 187 | 188 | 0xFFFE is inserted into the MAC address between the manufacturer ID and the board ID. 189 | seventh bit of the first byte is reversed. 190 | 191 | 192 | Lets make an example with following MAC address 00:0C:42:28:79:45. Ipv6eui64.png Image above illustrates conversation process. When the result is converted to colon-hexadecimal notation, we get the interface identifier 20C:42FF:FE28:7945. As the result, corresponds link-local address is 193 | 194 | FE80::20C:42FF:FE28:7945/64 195 | 196 | 197 | In RouterOS, if the eui-64 parameter of an address is configured, the last 64 bits of that address will be automatically generated and updated using interface identifier. The last bits must be configured to be zero for this case. Example: 198 | 199 | [admin@MikroTik] > ipv6 address add address=fc00:3::/64 interface=ether3 eui-64=yes 200 | [admin@MikroTik] > ipv6 address print 201 | Flags: X - disabled, I - invalid, D - dynamic, G - global, L - link-local 202 | # ADDRESS INTERFACE ADVERTISE 203 | ... 204 | 5 G fc00:3::20c:42ff:fe1d:3d4/64 ether3 yes 205 | [admin@MikroTik] > interface ethernet set ether3 mac-address=10:00:00:00:00:01 206 | [admin@MikroTik] > ipv6 address print 207 | Flags: X - disabled, I - invalid, D - dynamic, G - global, L - link-local 208 | # ADDRESS INTERFACE ADVERTISE 209 | ... 210 | 5 G fc00:3::1200:ff:fe00:1/64 ether3 yes 211 | 212 | Properties 213 | Property Description 214 | address (Address/Netmask; Default: ) Ipv6 address. Allowed netmask range is 0..128. Address can also be constructed from the pool if from-pool property is specified. 215 | For example if address is set to ::1/64 then address will be constructed as follows ::1/64 216 | advertise (yes | no; Default: no) Whether to enable stateless address configuration. The prefix of that address is automatically advertised to hosts using ICMPv6 protocol. The option is set by default for addresses with prefix length 64. Read more >> 217 | comment (string; Default: ) Descriptive name of an item 218 | disabled (yes | no; Default: no) Whether address is disabled or not. By default it is disabled 219 | eui-64 (yes | no; Default: no) Whether to calculate EUI-64 address and use it as last 64 bits of the IPv6 address. Read more >> 220 | from-pool (string; Default: ) Name of the pool from which prefix will be taken to construct IPv6 address taking last part of the address from address property. See example >> 221 | interface (string; Default: ) Name of an interface on which Ipv6 address is set. 222 | 223 | 224 | Read-only properties 225 | Property Description 226 | actual-interface (string) Actual interface on which address is set up. For example, if address was configured on ethernet interface and ethernet interface was added to bridge, then actual interface is bridge not ethernet. 227 | dynamic (yes | no) Whether address is dynamically created 228 | global (yes | no) Whether address is global 229 | invalid (yes | no) 230 | link-local (yes | no) Whether address is link local 231 | Examples 232 | Manual address configuration 233 | 234 | This example shows how to set up simple addressing with global IPv6 addresses between two routers. 235 | Ipv6-simple-address-example.png 236 | 237 | 238 | R1 configuration: 239 | 240 | /ipv6 address 241 | add address=2001:DB8::1/64 interface=ether1 advertise=no 242 | 243 | 244 | R2 configuration: 245 | 246 | /ipv6 address 247 | add address=2001:DB8::2/64 interface=ether1 advertise=no 248 | 249 | Check address list 250 | 251 | [admin@R1] /ipv6 address> print 252 | Flags: X - disabled, I - invalid, D - dynamic, G - global, L - link-local 253 | # ADDRESS FROM-POOL INTERFACE ADVERTISE 254 | 0 G 2001:db8::1/64 ether1 no 255 | 3 DL fe80::219:d1ff:fe39:3535/64 ether1 no 256 | 257 | Notice that our added address has G flag indicated that this address can be globally routed. We also have link local address on the interface which is created automatically for every IPv6 capable interface. 258 | 259 | 260 | Test connectivity 261 | 262 | [admin@R1] /ipv6 address> /ping 2001:DB8::2 263 | HOST SIZE TTL TIME STATUS 264 | 2001:db8::2 56 64 12ms echo reply 265 | 2001:db8::2 56 64 0ms echo reply 266 | sent=2 received=2 packet-loss=0% min-rtt=0ms avg-rtt=6ms max-rtt=12ms 267 | 268 | [ Top | Back to Content ] 269 | Categories: 270 | 271 | ManualIPv6 272 | 273 | Navigation menu 274 | 275 | Log in 276 | 277 | Manual 278 | Discussion 279 | 280 | Read 281 | View source 282 | View history 283 | 284 | Navigation 285 | 286 | Main Page 287 | Recent changes 288 | 289 | Tools 290 | 291 | What links here 292 | Related changes 293 | Special pages 294 | Printable version 295 | Permanent link 296 | Page information 297 | 298 | This page was last modified on 1 June 2012, at 14:22. 299 | This page has been accessed 83,296 times. 300 | 301 | Privacy policy 302 | About MikroTik Wiki 303 | Disclaimers 304 | 305 | Powered by MediaWiki 306 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const assert = require('assert'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | 7 | // require lib 8 | const whois = require('./../lib/whois'); 9 | 10 | 11 | describe('ip2long()', () => { 12 | it('should convert 192.0.34.166 to 3221234342', done => { 13 | assert.equal(whois.ip2long('192.0.34.166'), 3221234342); 14 | done(); 15 | }); 16 | it('should convert 0xC0.0x00.0x02.0xEB to 3221226219', done => { 17 | assert.equal(whois.ip2long('0xC0.0x00.0x02.0xEB'), 3221226219); 18 | done(); 19 | }); 20 | it('should convert 0xC00002EB to 3221226219', done => { 21 | assert.equal(whois.ip2long('0xC00002EB'), 3221226219); 22 | done(); 23 | }); 24 | it('should convert 3221226219 to 3221226219', done => { 25 | assert.equal(whois.ip2long('3221226219'), 3221226219); 26 | done(); 27 | }); 28 | it('should convert 0.0xABCDEF to 11259375', done => { 29 | assert.equal(whois.ip2long('0.0xABCDEF'), 11259375); 30 | done(); 31 | }); 32 | it('should convert 0300.0000.0002.0353 to 3221226219', done => { 33 | assert.equal(whois.ip2long('0300.0000.0002.0353'), 3221226219); 34 | done(); 35 | }); 36 | it('should convert 030000001353 to 3221226219', done => { 37 | assert.equal(whois.ip2long('030000001353'), 3221226219); 38 | done(); 39 | }); 40 | it('should not convert 255.255.255.256', done => { 41 | assert.equal(whois.ip2long('255.255.255.256'), false); 42 | done(); 43 | }); 44 | }); 45 | 46 | describe('isIP()', () => { 47 | it('should detect that 192.0.34.166 is IP', done => { 48 | assert.equal(whois.isIP('192.0.34.166'), true); 49 | done(); 50 | }); 51 | it('should detect that 2000:: is IP', done => { 52 | assert.equal(whois.isIP('2000::'), true); 53 | done(); 54 | }); 55 | it('should detect that 3FFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF is IP', done => { 56 | assert.equal(whois.isIP('3FFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF'), true); 57 | done(); 58 | }); 59 | it('should detect that 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d is IP', done => { 60 | assert.equal(whois.isIP('2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d'), true); 61 | done(); 62 | }); 63 | it('should detect that 2001:0db8:0000:0000:0000:0000:ae21:ad12 is IP', done => { 64 | assert.equal(whois.isIP('2001:0db8:0000:0000:0000:0000:ae21:ad12'), true); 65 | done(); 66 | }); 67 | it('should detect that 2001:db8::ae21:ad12 is IP', done => { 68 | assert.equal(whois.isIP('2001:db8::ae21:ad12'), true); 69 | done(); 70 | }); 71 | it('should detect that 0000:0000:0000:0000:0000:0000:ae21:ad12 is IP', done => { 72 | assert.equal(whois.isIP('0000:0000:0000:0000:0000:0000:ae21:ad12'), true); 73 | done(); 74 | }); 75 | it('should detect that 192.168.4.114.xip.io is not IP', done => { 76 | assert.equal(whois.isIP('192.168.4.114.xip.io'), false); 77 | done(); 78 | }); 79 | it('should detect that 3221234342 is not IP', done => { 80 | assert.equal(whois.isIP('3221234342'), false); 81 | done(); 82 | }); 83 | it('should detect that 0.0xABCDEF is not IP', done => { 84 | assert.equal(whois.isIP('0.0xABCDEF'), false); 85 | done(); 86 | }); 87 | it('should detect that 255.255.255.256 is not IP', done => { 88 | assert.equal(whois.isIP('255.255.255.256'), false); 89 | done(); 90 | }); 91 | it('should detect that ::aq21:ad12 is not IP', done => { 92 | assert.equal(whois.isIP('::aq21:ad12'), false); 93 | done(); 94 | }); 95 | it('should detect that ad12 is not IP', done => { 96 | assert.equal(whois.isIP('ad12'), false); 97 | done(); 98 | }); 99 | }); 100 | 101 | 102 | describe('isDomain()', () => { 103 | it('should detect that xinit.ru is domain name', done => { 104 | assert.equal(whois.isDomain('xinit.ru'), true); 105 | done(); 106 | }); 107 | it('should detect that xinit.ru. is domain name', done => { 108 | assert.equal(whois.isDomain('xinit.ru.'), true); 109 | done(); 110 | }); 111 | it('should detect that xinit.russia is domain name', done => { 112 | assert.equal(whois.isDomain('xinit.russia'), true); 113 | done(); 114 | }); 115 | it('should detect that xin-it.co is domain name', done => { 116 | assert.equal(whois.isDomain('xin-it.co'), true); 117 | done(); 118 | }); 119 | it('should detect that xin--it.co is domain name', done => { 120 | assert.equal(whois.isDomain('xin--it.co'), true); 121 | done(); 122 | }); 123 | it('should detect that 4012.ru is domain name', done => { 124 | assert.equal(whois.isDomain('4012.ru'), true); 125 | done(); 126 | }); 127 | it('should detect that пример.рф is domain name', done => { 128 | assert.equal(whois.isDomain('пример.рф'), true); 129 | done(); 130 | }); 131 | it('should detect that пример.su is domain name', done => { 132 | assert.equal(whois.isDomain('пример.su'), true); 133 | done(); 134 | }); 135 | it('should detect that www.xinit.ru is domain name', done => { 136 | assert.equal(whois.isDomain('www.xinit.ru'), true); 137 | done(); 138 | }); 139 | it('should detect that ввв.xinit.ru is domain name', done => { 140 | assert.equal(whois.isDomain('ввв.xinit.ru'), true); 141 | done(); 142 | }); 143 | it('should detect that xinit.рф is domain name', done => { 144 | assert.equal(whois.isDomain('xinit.рф'), true); 145 | done(); 146 | }); 147 | it('should detect that 127.0.0.1.ru is domain name', done => { 148 | assert.equal(whois.isDomain('127.0.0.1.ru'), true); 149 | done(); 150 | }); 151 | it('should detect that s.xinit.ru is domain name', done => { 152 | assert.equal(whois.isDomain('s.xinit.ru'), true); 153 | done(); 154 | }); 155 | it('should detect that XN--80ABERRY5A.XN--P1AI is domain name', done => { 156 | assert.equal(whois.isDomain('XN--80ABERRY5A.XN--P1AI'), true); 157 | done(); 158 | }); 159 | it('should detect that . is not domain name', done => { 160 | assert.equal(whois.isDomain('.'), false); 161 | done(); 162 | }); 163 | it('should detect that 0.r is not domain name', done => { 164 | assert.equal(whois.isDomain('0.r'), false); 165 | done(); 166 | }); 167 | it('should detect that example.0 is not domain name', done => { 168 | assert.equal(whois.isDomain('example.0'), false); 169 | done(); 170 | }); 171 | it('should detect that example.01 is not domain name', done => { 172 | assert.equal(whois.isDomain('example.01'), false); 173 | done(); 174 | }); 175 | it('should detect that 01.02 is not domain name', done => { 176 | assert.equal(whois.isDomain('01.02'), false); 177 | done(); 178 | }); 179 | it('should detect that -ex.ru is not domain name', done => { 180 | assert.equal(whois.isDomain('-ex.ru'), false); 181 | done(); 182 | }); 183 | it('should detect that ex-.ru is not domain name', done => { 184 | assert.equal(whois.isDomain('ex-.ru'), false); 185 | done(); 186 | }); 187 | it('should detect that 127.0.0.1 is not domain name', done => { 188 | assert.equal(whois.isDomain('127.0.0.1'), false); 189 | done(); 190 | }); 191 | it('should detect that ru is not domain name', done => { 192 | assert.equal(whois.isDomain('ru'), false); 193 | done(); 194 | }); 195 | it('should detect that ru. is not domain name', done => { 196 | assert.equal(whois.isDomain('ru.'), false); 197 | done(); 198 | }); 199 | it('should detect that .ru. is not domain name', done => { 200 | assert.equal(whois.isDomain('.ru.'), false); 201 | done(); 202 | }); 203 | it('should detect that .ru is domain not name', done => { 204 | assert.equal(whois.isDomain('.ru'), false); 205 | done(); 206 | }); 207 | it('should detect that .xinit.ru is not domain name', done => { 208 | assert.equal(whois.isDomain('.xinit.ru'), false); 209 | done(); 210 | }); 211 | it('should detect that s..xinit.ru is not domain name', done => { 212 | assert.equal(whois.isDomain('s..xinit.ru'), false); 213 | done(); 214 | }); 215 | }); 216 | 217 | 218 | describe('reverse()', () => { 219 | it('should reverse 5.135.189.181', done => { 220 | whois.reverse('5.135.189.181') 221 | .then(hostnames => { 222 | assert.equal(Array.isArray(hostnames), true); 223 | done(); 224 | }) 225 | .catch(err => done(err)); 226 | }); 227 | it('should reverse 151.80.40.204', done => { 228 | whois.reverse('151.80.40.204') 229 | .then(hostnames => { 230 | assert.equal(Array.isArray(hostnames), true); 231 | done(); 232 | }) 233 | .catch(err => done(err)); 234 | }); 235 | it('should reverse 83.219.135.245', done => { 236 | whois.reverse('83.219.135.245') 237 | .then(hostnames => { 238 | assert.equal(Array.isArray(hostnames), true); 239 | done(); 240 | }) 241 | .catch(err => done(err)); 242 | }); 243 | it('should reverse 144.76.195.239', done => { 244 | whois.reverse('144.76.195.239') 245 | .then(hostnames => { 246 | assert.equal(Array.isArray(hostnames), true); 247 | done(); 248 | }) 249 | .catch(err => done(err)); 250 | }); 251 | it('should reverse 82.192.95.170', done => { 252 | whois.reverse('82.192.95.170') 253 | .then(hostnames => { 254 | assert.equal(Array.isArray(hostnames), true); 255 | done(); 256 | }) 257 | .catch(err => done(err)); 258 | }); 259 | it('should reverse 77.88.55.66', done => { 260 | whois.reverse('77.88.55.66') 261 | .then(hostnames => { 262 | assert.equal(Array.isArray(hostnames), true); 263 | done(); 264 | }) 265 | .catch(err => done(err)); 266 | }); 267 | it('should reverse 127.0.0.1', done => { 268 | whois.reverse('127.0.0.1') 269 | .then(hostnames => { 270 | assert.equal(Array.isArray(hostnames), true); 271 | done(); 272 | }) 273 | .catch(err => done(err)); 274 | }); 275 | it('should not reverse xinit.ru', done => { 276 | whois.reverse('xinit.ru') 277 | .then(hostnames => { 278 | done(new Error()); 279 | }) 280 | .catch(err => done()); 281 | }); 282 | it('should not reverse "ip"', done => { 283 | whois.reverse('ip') 284 | .then(hostnames => { 285 | done(new Error()); 286 | }) 287 | .catch(err => done()); 288 | }); 289 | }); 290 | 291 | 292 | describe('nslookup()', function() { 293 | this.timeout(10000); 294 | 295 | it('should nslookup xinit.ru', done => { 296 | whois.nslookup('xinit.ru') 297 | .then(addresses => { 298 | assert.equal(typeof addresses, 'object'); 299 | done(); 300 | }) 301 | .catch(err => done(err)); 302 | }); 303 | it('should nslookup google.com', done => { 304 | whois.nslookup('google.com') 305 | .then(addresses => { 306 | assert.equal(typeof addresses, 'object'); 307 | done(); 308 | }) 309 | .catch(err => done(err)); 310 | }); 311 | it('should nslookup yandex.ru', done => { 312 | whois.nslookup('yandex.ru') 313 | .then(addresses => { 314 | assert.equal(typeof addresses, 'object'); 315 | done(); 316 | }) 317 | .catch(err => done(err)); 318 | }); 319 | it('should nslookup habr.ru', done => { 320 | whois.nslookup('habr.ru') 321 | .then(addresses => { 322 | assert.equal(typeof addresses, 'object'); 323 | done(); 324 | }) 325 | .catch(err => done(err)); 326 | }); 327 | it('should nslookup vk.com', done => { 328 | whois.nslookup('vk.com') 329 | .then(addresses => { 330 | assert.equal(typeof addresses, 'object'); 331 | done(); 332 | }) 333 | .catch(err => done(err)); 334 | }); 335 | it('should nslookup XN--80ABERRY5A.XN--P1AI', done => { 336 | whois.nslookup('XN--80ABERRY5A.XN--P1AI') 337 | .then(addresses => { 338 | assert.equal(typeof addresses, 'object'); 339 | done(); 340 | }) 341 | .catch(err => done(err)); 342 | }); 343 | it('should nslookup пример.su', done => { 344 | whois.nslookup('пример.su') 345 | .then(addresses => { 346 | assert.equal(typeof addresses, 'object'); 347 | done(); 348 | }) 349 | .catch(err => done(err)); 350 | }); 351 | it('should nslookup xinit.рф', done => { 352 | whois.nslookup('xinit.рф') 353 | .then(addresses => { 354 | assert.equal(typeof addresses, 'object'); 355 | done(); 356 | }) 357 | .catch(err => done(err)); 358 | }); 359 | it('should not nslookup IP address 83.219.135.207', done => { 360 | whois.nslookup('83.219.135.207') 361 | .then(addresses => { 362 | done(new Error()); 363 | }) 364 | .catch(err => done()); 365 | }); 366 | }); 367 | 368 | 369 | describe('whois()', () => { 370 | it('should whois xinit.ru', done => { 371 | whois.whois('xinit.ru') 372 | .then(data => { 373 | assert.notEqual(data, ''); 374 | done(); 375 | }) 376 | .catch(err => done(err)); 377 | }); 378 | it('should whois google.com', done => { 379 | whois.whois('google.com') 380 | .then(data => { 381 | assert.notEqual(data, ''); 382 | done(); 383 | }) 384 | .catch(err => done(err)); 385 | }); 386 | it('should whois yandex.ru', done => { 387 | whois.whois('yandex.ru') 388 | .then(data => { 389 | assert.notEqual(data, ''); 390 | done(); 391 | }) 392 | .catch(err => done(err)); 393 | }); 394 | it('should whois habr.ru', done => { 395 | whois.whois('habr.ru') 396 | .then(data => { 397 | assert.notEqual(data, ''); 398 | done(); 399 | }) 400 | .catch(err => done(err)); 401 | }); 402 | it('should whois vk.com', done => { 403 | whois.whois('vk.com') 404 | .then(data => { 405 | assert.notEqual(data, ''); 406 | done(); 407 | }) 408 | .catch(err => done(err)); 409 | }); 410 | it('should whois XN--80ABERRY5A.XN--P1AI', done => { 411 | whois.whois('XN--80ABERRY5A.XN--P1AI') 412 | .then(data => { 413 | assert.notEqual(data, ''); 414 | done(); 415 | }) 416 | .catch(err => done(err)); 417 | }); 418 | it('should whois xn--80aberry5a.xn--p1ai', done => { 419 | whois.whois('xn--80aberry5a.xn--p1ai') 420 | .then(data => { 421 | assert.notEqual(data, ''); 422 | done(); 423 | }) 424 | .catch(err => done(err)); 425 | }); 426 | it('should whois пример.su', done => { 427 | whois.whois('пример.su') 428 | .then(data => { 429 | assert.notEqual(data, ''); 430 | done(); 431 | }) 432 | .catch(err => done(err)); 433 | }); 434 | it('should whois xinit.рф', done => { 435 | whois.whois('xinit.рф') 436 | .then(data => { 437 | assert.notEqual(data, ''); 438 | done(); 439 | }) 440 | .catch(err => done(err)); 441 | }); 442 | it('should whois 83.219.135.207', done => { 443 | whois.whois('83.219.135.207') 444 | .then(data => { 445 | assert.notEqual(data, ''); 446 | done(); 447 | }) 448 | .catch(err => done(err)); 449 | }); 450 | it('should whois 83.219.135.307', done => { 451 | whois.whois('83.219.135.307') 452 | .then(data => { 453 | assert.notEqual(data, ''); 454 | done(); 455 | }) 456 | .catch(err => done(err)); 457 | }); 458 | it('should whois 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d', done => { 459 | whois.whois('2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d') 460 | .then(data => { 461 | assert.notEqual(data, ''); 462 | done(); 463 | }) 464 | .catch(err => done(err)); 465 | }); 466 | it('should whois " "', done => { 467 | whois.whois(' ') 468 | .then(data => { 469 | assert.notEqual(data, ''); 470 | done(); 471 | }) 472 | .catch(err => done(err)); 473 | }); 474 | it('should not whois "1"', done => { 475 | whois.whois(1) 476 | .then(data => { 477 | done(new Error()); 478 | }) 479 | .catch(err => done()); 480 | }); 481 | }); 482 | 483 | 484 | describe('torInfo()', () => { 485 | const testVectors = [ 486 | {ip: '103.200.210.66', isIP: true, isTOR: true }, 487 | {ip: '103.28.53.138' , isIP: true, isTOR: true }, 488 | {ip: '104.244.76.13' , isIP: true, isTOR: true }, 489 | {ip: '109.9.160.104' , isIP: true, isTOR: true }, 490 | 491 | {ip: '108.12.223.185' , isIP: true, isTOR: false}, 492 | {ip: '162.243.123.220', isIP: true, isTOR: false}, 493 | {ip: '23.80.226.4' , isIP: true, isTOR: false}, 494 | {ip: '130.253.21.123' , isIP: true, isTOR: false}, 495 | {ip: '188.138.88.168' , isIP: true, isTOR: false}, 496 | {ip: '8.8.8.8' , isIP: true, isTOR: false}, 497 | {ip: '8.8.4.4' , isIP: true, isTOR: false}, 498 | {ip: '127.0.0.1' , isIP: true, isTOR: false}, 499 | {ip: '5.135.189.181' , isIP: true, isTOR: false}, 500 | 501 | {ip: 'xinit.ru' , isIP: false, isTOR: false}, 502 | {ip: 'example.com' , isIP: false, isTOR: false}, 503 | {ip: 'test string' , isIP: false, isTOR: false}, 504 | {ip: '' , isIP: false, isTOR: false}, 505 | {ip: 0 , isIP: false, isTOR: false} 506 | ]; 507 | 508 | testVectors.forEach(test => { 509 | it(`should ${test.isTOR ? '' : 'not '}define '${test.ip}' as TOR node`, done => { 510 | whois.torInfo(test.ip) 511 | .then(data => { 512 | if (test.isTOR && data === null) 513 | done(new Error('There should be some info about Tor node')); 514 | 515 | else if (!test.isTOR && data !== null) 516 | done(new Error('There should not be any info about non-Tor node')); 517 | 518 | else 519 | done(); 520 | }) 521 | .catch(err => { 522 | if (test.isIP) 523 | done(new Error('Should not catch() on IP-addresses')); 524 | else 525 | done(); 526 | }); 527 | }); 528 | }); 529 | }); 530 | 531 | 532 | describe('extractIP()', function() { 533 | this.timeout(10000); 534 | 535 | const testVectors = [ 536 | ` 537 | 77.109.141.140 37.187.130.68 188.40.143.7 95.174.227.96 538 | 5.135.155.121 127.0.0.1 83.219.135.207 1270.0.0.1 539 | 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 104.16.0.0/12 540 | 108.162.192.0/18 141.101.64.0/18 162.158.0.0/15 172.64.0.0/13 541 | 173.245.48.0/20 188.114.96.0/20 190.93.240.0/20 197.234.240.0/22 542 | 198.41.128.0/17 199.27.128.0/21 543 | 2000:: 3FFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF 544 | 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d 2001:0db8:0000:0000:0000:0000:ae21:ad12 545 | 2001:db8::ae21:ad12 0000:0000:0000:0000:0000:0000:ae21:ad12 546 | 2400:cb00::/32 2405:8100::/32 547 | 2405:b500::/32 2606:4700::/32 548 | 2803:f800::/32 549 | `, 550 | fs.readFileSync(path.join('test', 'extractIP_test', '1.txt'), {encoding: 'utf8'}), 551 | fs.readFileSync(path.join('test', 'extractIP_test', '2.txt'), {encoding: 'utf8'}), 552 | fs.readFileSync(path.join('test', 'extractIP_test', '3.txt'), {encoding: 'utf8'}), 553 | fs.readFileSync(path.join('test', 'extractIP_test', '4.txt'), {encoding: 'utf8'}) 554 | ]; 555 | 556 | testVectors.forEach(str => { 557 | it(`should extract array of IP addresses from string of ${str.length} bytes`, done => { 558 | whois.extractIP(str) 559 | .then(data => { 560 | if (!Array.isArray(data) || data.length === 0) 561 | done(new Error('There should not be empty IP list')); 562 | else 563 | done(); 564 | }) 565 | .catch(err => done(err)); 566 | }); 567 | }); 568 | }); 569 | 570 | 571 | describe('geoInit()', function() { 572 | this.timeout(10000); 573 | 574 | it('should properly initialize Geo functions', done => { 575 | whois.geoInit(path.join('test', 'GeoIP')) 576 | .then(() => { 577 | describe('geoInfo()', () => { 578 | const testVectors = [ 579 | '201.11.139.220', 580 | '200.3.223.231', 581 | '107.170.65.197', 582 | '195.154.215.240', 583 | '78.46.112.219', 584 | '37.60.214.34', 585 | '5.135.189.181', 586 | '93.228.75.194', 587 | '2001:41d0:8:c6b5::1', 588 | '103.200.210.66', 589 | '103.28.53.138', 590 | '104.244.76.13', 591 | '109.9.160.104', 592 | '220.207.203.173', 593 | '220.224.17.8', 594 | '220.224.27.20', 595 | '220.224.27.236', 596 | '220.224.3.229', 597 | '220.224.33.81', 598 | '220.224.37.164', 599 | '220.224.38.211', 600 | '220.224.4.185', 601 | '121.200.103.190', 602 | '109.111.139.45', 603 | '77.109.141.140', 604 | '37.187.130.68', 605 | '121.69.113.7', 606 | '124.53.86.188', 607 | '83.167.112.7', 608 | '91.76.97.133', 609 | '212.154.238.181', 610 | '220.224.43.226', 611 | '220.224.4.83', 612 | '220.224.50.32', 613 | '220.224.69.230', 614 | '220.224.73.67', 615 | '220.224.76.132', 616 | '220.224.80.55', 617 | '86.39.37.158', 618 | ]; 619 | 620 | testVectors.forEach(ip => { 621 | it(`should give Geo info for ${ip}`, done => { 622 | whois.geoInfo(ip) 623 | .then(data => { 624 | if (data === null) 625 | done(new Error('Should return some Geo info')); 626 | else { 627 | done(); 628 | } 629 | //console.log(data); 630 | }) 631 | .catch(err => done(err)); 632 | }); 633 | }); 634 | }); 635 | done(); 636 | }) 637 | .catch(err => done(err)); 638 | }); 639 | }); 640 | 641 | 642 | describe('bgpInfo()', function() { 643 | this.timeout(10000); 644 | 645 | const testVectors = [ 646 | '183.88.220.162', 647 | '121.200.103.190', 648 | '109.111.139.45', 649 | '77.109.141.140', 650 | '37.187.130.68', 651 | '121.69.113.7', 652 | '124.53.86.188', 653 | '83.167.112.7', 654 | '91.76.97.133', 655 | '212.154.238.181', 656 | '201.11.139.220', 657 | '200.3.223.231', 658 | '107.170.65.197', 659 | '195.154.215.240', 660 | '78.46.112.219', 661 | '37.60.214.34', 662 | '5.135.189.181', 663 | '93.228.75.194', 664 | '2001:41d0:8:c6b5::1' 665 | ]; 666 | 667 | testVectors.forEach(ip => { 668 | it(`should give BGP info for ${ip}`, done => { 669 | whois.bgpInfo(ip) 670 | .then(data => { 671 | if (data === null) 672 | done(new Error('Should return some BGP related info')); 673 | else 674 | done(); 675 | }) 676 | .catch(err => done(err)); 677 | }); 678 | }); 679 | }); 680 | 681 | 682 | 683 | describe('info()', function() { 684 | this.timeout(10000); 685 | 686 | const testVectors = [ 687 | '121.200.103.190', 688 | 'google.com', 689 | 'xinit.ru', 690 | '37.187.130.68', 691 | '8.8.8.8', 692 | 'raccoongate.com', 693 | '5.135.189.181', 694 | '2001:41d0:8:c6b5::1' 695 | ]; 696 | 697 | testVectors.forEach(host => { 698 | it(`should give info for ${host}`, done => { 699 | whois.info(host) 700 | .then(data => { 701 | if (data === null) 702 | done(new Error('Should return some info')); 703 | else 704 | done(); 705 | }) 706 | .catch(err => done(err)); 707 | }); 708 | }); 709 | }); 710 | 711 | -------------------------------------------------------------------------------- /lib/whois.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const dns = require('dns').promises; 3 | const net = require('net'); 4 | const https = require('https'); 5 | const fs = require('fs'); 6 | const zlib = require('zlib'); 7 | const jszip = require('jszip'); 8 | const tar = require('tar-stream'); 9 | const punycode = require('punycode'); 10 | const path = require('path'); 11 | const ip2loc = require('ip2location-nodejs'); 12 | const ip2proxy = require('ip2proxy-nodejs'); 13 | const mmdbReader = require('@maxmind/geoip2-node').Reader; 14 | const node_whois = require('whois'); 15 | 16 | 17 | // GeoIP info object 18 | let maxMind = null; 19 | let maxMind_asn = null; 20 | 21 | // show whether geoIP was initialized or not 22 | let geoInitialized = false; 23 | 24 | 25 | /** 26 | * A JavaScript equivalent of PHP's ip2long(). Convert IPv4 address in dotted 27 | * notation to 32-bit long integer. 28 | * You can pass IP in all possible representations, i.e.: 29 | * 192.0.34.166 30 | * 0xC0.0x00.0x02.0xEB 31 | * 0xC00002EB 32 | * 3221226219 33 | * 0.0xABCDEF 34 | * 255.255.255.256 35 | * 0300.0000.0002.0353 36 | * 030000001353 37 | * 38 | * @param {string} ip IPv4-address in one of possible representations. 39 | * @return {number} The 32-bit number notation of IP-address expressed in 40 | * decimal. 41 | */ 42 | const ip2long = ip => { 43 | // discuss at: http://phpjs.org/functions/ip2long/ 44 | // original by: Waldo Malqui Silva 45 | // improved by: Victor 46 | // improved by: Alexander Zubakov 47 | 48 | // PHP allows decimal, octal, and hexadecimal IP components. 49 | // PHP allows between 1 (e.g. 127) to 4 (e.g 127.0.0.1) components. 50 | ip = ip.match( 51 | /^([1-9]\d*|0[0-7]*|0x[\da-f]+)(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?$/i 52 | ); 53 | 54 | // invalid format. 55 | if (ip === null) return false; 56 | 57 | // reuse IP variable for component counter. 58 | ip[0] = 0; 59 | for (let i = 1; i <= 4; i++) { 60 | // calculate radix for parseInt() 61 | let radix = 10; 62 | 63 | // radix should be 8 or 16 64 | if (typeof ip[i] !== 'undefined' && ip[i].length > 1 && ip[i][0] === '0') 65 | radix = ip[i][1].toLowerCase() === 'x' ? 16 : 8; 66 | 67 | ip[0] += !! ((ip[i] || '').length); 68 | ip[i] = parseInt(ip[i], radix) || 0; 69 | } 70 | 71 | // continue to use IP for overflow values. 72 | // PHP does not allow any component to overflow. 73 | ip.push(256, 256, 256, 256); 74 | 75 | // recalculate overflow of last component supplied to make up for missing components. 76 | ip[4 + ip[0]] *= Math.pow(256, 4 - ip[0]); 77 | 78 | if (ip[1] >= ip[5] || ip[2] >= ip[6] || ip[3] >= ip[7] || ip[4] >= ip[8]) 79 | return false; 80 | 81 | return ip[1] * (ip[0] === 1 || 16777216) + ip[2] * (ip[0] <= 2 || 65536) + ip[3] * (ip[0] <= 3 || 256) + ip[4] * 1; 82 | }; 83 | 84 | 85 | /** 86 | * Detect if host is correct IP-address. 87 | * 88 | * @param {string} host String to test. 89 | * @return {boolean} True if host is correct IP-address or false otherwise. 90 | */ 91 | const isIP = host => net.isIP(host) !== 0; 92 | 93 | 94 | /** 95 | * Detect if host is correct domain name. It can't test IDN's. And it can't 96 | * define if domain name is really exist or can exist. This function just 97 | * performs syntax check. 98 | * 99 | * @param {string} host String to test. 100 | * @return {boolean} True if domain name is correct or false otherwise. 101 | */ 102 | const isDomain = host => { 103 | /* 104 | Function grabbed with little modifications from 105 | https://github.com/chriso/validator.js/blob/master/lib/isFQDN.js 106 | which is a part of `validator` npm package: 107 | https://www.npmjs.com/package/validator 108 | */ 109 | 110 | if (host[host.length - 1] === '.') { 111 | host = host.substring(0, host.length - 1); 112 | } 113 | 114 | const parts = host.split('.'); 115 | 116 | for (let i = 0; i < parts.length; i++) { 117 | if (parts[i].length > 63) { 118 | return false; 119 | } 120 | } 121 | 122 | const tld = parts.pop(); 123 | 124 | if (!parts.length || !/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) { 125 | return false; 126 | } 127 | 128 | if (/[\s\u2002-\u200B\u202F\u205F\u3000\uFEFF\uDB40\uDC20]/.test(tld)) { 129 | return false; 130 | } 131 | 132 | for (let i = 0; i < parts.length; i++) { 133 | const part = parts[i]; 134 | 135 | if (!/^[a-z\u00a1-\uffff0-9-]+$/i.test(part)) { 136 | return false; 137 | } 138 | 139 | if (/[\uff01-\uff5e]/.test(part)) { 140 | return false; 141 | } 142 | 143 | if (part[0] === '-' || part[part.length - 1] === '-') { 144 | return false; 145 | } 146 | } 147 | 148 | return true; 149 | }; 150 | 151 | 152 | /** 153 | * Get host info of domain name like `host -a` command. 154 | * 155 | * @param {string} host Domain name. 156 | * @return {Promise} Promise where then() takes function with object like this: 157 | * { 158 | * 'A' : ['IPv4-addresses'], 159 | * 'AAAA' : ['IPv6-addresses'], 160 | * 'MX' : ['MX-records' ], 161 | * 'TXT' : ['TXT-records' ], 162 | * 'SRV' : ['SRV-records' ], 163 | * 'NS' : ['NS-records' ], 164 | * 'CNAME': ['CNAME-records' ], 165 | * 'NAPTR': ['CNAME-records' ], 166 | * 'PTR': ['CNAME-records' ], 167 | * 'SOA' : ['SOA-records' ] 168 | * } 169 | */ 170 | const nslookup = async host => { 171 | if (!isDomain(host)) { 172 | throw new Error(`Not domain name: ${host}`); 173 | } 174 | 175 | // need for IDN domain names but doesn't matter if used on ASCII names 176 | host = punycode.toASCII(host); 177 | 178 | // types of requests to perform (all possible types) 179 | const rrtypes = ['A', 'AAAA', 'CNAME', 'MX', 'NAPTR', 'NS', 'PTR', 'SOA', 'SRV', 'TXT']; 180 | 181 | // promises to resolve domain name of all possible rrtypes 182 | const resolves = rrtypes.map(async rrtype => { 183 | let request = null; 184 | try { 185 | request = await dns.resolve(host, rrtype); 186 | } catch(err) { 187 | request = null; 188 | } 189 | 190 | return request; 191 | }); 192 | 193 | // perform resolve 194 | const resolved = await Promise.all(resolves); 195 | 196 | // collect all rrtypes resolves in one object 197 | const result = resolved.reduce((prev, curr, index) => { 198 | if (curr) { 199 | if (rrtypes[index] === 'TXT') { 200 | curr = curr.map(lines => lines.join('\n')); 201 | } 202 | 203 | prev[rrtypes[index]] = curr; 204 | } 205 | 206 | return prev; 207 | }, {}); 208 | 209 | return result; 210 | }; 211 | 212 | 213 | /** 214 | * Perform whois request. 215 | * 216 | * @param {string} host Domain name or IP-address. 217 | * @return {object} Promise where then() takes function with whois text. 218 | */ 219 | const whois = (host, options = {}) => { 220 | return new Promise((resolve, reject) => { 221 | node_whois.lookup(host, options, (err, data) => { 222 | if (err) { 223 | return void reject(err); 224 | } 225 | 226 | return void resolve(data); 227 | }); 228 | }); 229 | }; 230 | 231 | 232 | /** 233 | * Reverse IP address for some special DNS requests. Works both for IPv4 and 234 | * IPv6. 235 | * 236 | * Examples: 237 | * IPv4: 1.2.3.4 -> 4.3.2.1 238 | * IPv6: 239 | * 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d -> 240 | * d.5.6.7.0.a.7.0.e.2.a.8.4.3.f.1.7.d.9.0.3.a.1.1.8.d.b.0.1.0.0.2 241 | * 2001:db8::ae21:ad12 -> 242 | * 2.1.d.a.1.2.e.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.d.b.0.1.0.0.2 243 | * 244 | * @param {string} ip IP address to reverse. 245 | * @return {string} Reversed IP address. If parameter is not IP-address then 246 | * return itself. 247 | */ 248 | const ipReverse = ip => { 249 | const ipaddr = ip.trim(); 250 | 251 | // IPv4 252 | if (net.isIPv4(ipaddr)) { 253 | return ipaddr 254 | .split('.') 255 | .reverse() 256 | .join('.'); 257 | } 258 | 259 | // IPv6 260 | if (net.isIPv6(ipaddr)) { 261 | return ipaddr 262 | .split(':') 263 | .reduce((prev, curr, i, arr) => { 264 | // replace '::' with several '0000' 265 | if (curr.length === 0) { 266 | // should be 8 words (1 word is 2 bytes), and we have 267 | // array with several words and one empty element 268 | // so we replace empty element with words like '0000' to 269 | // satisfy total count of words 270 | return prev.concat('0000'.repeat(9 - arr.length)); 271 | } 272 | 273 | // left pad with '0' to length of 4 274 | return prev.concat('0'.repeat(4 - curr.length), curr); 275 | }, '') 276 | .split('') 277 | .reverse() 278 | .join('.'); 279 | } 280 | 281 | // not IP 282 | return ip; 283 | }; 284 | 285 | 286 | /** 287 | * Check if IP address is a TOR node. 288 | * 289 | * @param {string} ip IP-address. 290 | * @return {object} Promise where then() takes function with object like this: 291 | * { 292 | * 'nodename': 'Name of TOR node', 293 | * 'port' : [0], //port numbers of TOR node 294 | * 'exitNode': true //if true then this is exit node 295 | * } 296 | * If IP does not belong to TOR node then null will be passed 297 | * instead of described object. 298 | */ 299 | const torInfo = async ip => { 300 | const ipaddr = ip.trim(); 301 | 302 | if (!net.isIPv4(ipaddr)) { 303 | throw new Error(`Not valid IPv4: ${ipaddr}`); 304 | } 305 | 306 | // special server to check TOR node IPs 307 | const TORServer = 'tor.dan.me.uk'; 308 | 309 | // host to resolve using DNS query 310 | const host = ipReverse(ipaddr).concat('.', TORServer); 311 | 312 | // perform request 313 | let dns_A = null; 314 | try { 315 | dns_A = await dns.resolve(host, 'A'); 316 | } catch(err) { 317 | return null; 318 | } 319 | 320 | // not a TOR node 321 | if (!dns_A) { 322 | return null; 323 | } 324 | 325 | // get info about TOR node 326 | let dns_TXT = null; 327 | try { 328 | dns_TXT = (await dns.resolve(host, 'TXT')).map(lines => lines.join('')); 329 | } catch(err) { 330 | return null; 331 | } 332 | 333 | // no info about TOR node 334 | if (!dns_TXT || !(0 in dns_TXT)) { 335 | return null; 336 | } 337 | 338 | // RegExp to parse NS response 339 | const RE_INFO = /N:([^\/]+)\/P:(\d+(,\d+|))\/F:([EXABDFGHNRSUV]*)/; 340 | 341 | // parse NS response 342 | let matches = RE_INFO.exec(dns_TXT[0]); 343 | if (!matches) { 344 | return null; 345 | } 346 | 347 | const result = { 348 | nodename: matches[1], 349 | port : matches[2].split(','), 350 | exitNode: matches[4].indexOf('E') !== -1 || matches[4].indexOf('X') !== -1 351 | }; 352 | 353 | return result; 354 | }; 355 | 356 | 357 | /** 358 | * Reqular expressions to match IPv4 and IPv6 addresses. 359 | * Need for extractIP() and extractIPGen() 360 | */ 361 | const EXTRACT_IP_REGEXP = [ 362 | /((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])/g 363 | 364 | // get it from http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses 365 | , new RegExp( 366 | '(' + 367 | '([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|' + // 1:2:3:4:5:6:7:8 368 | '([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|' + // 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 369 | '([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|' + // 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 370 | '([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|' + // 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 371 | '([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|' + // 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 372 | '([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|' + // 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 373 | '[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|' + // 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 374 | ':((:[0-9a-fA-F]{1,4}){1,7}|:)|' + // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: 375 | '([0-9a-fA-F]{1,4}:){1,7}:|' + // 1:: 1:2:3:4:5:6:7:: 376 | 'fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|' + // fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index) 377 | '::(ffff(:0{1,4}){0,1}:){0,1}' + 378 | '((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}' + 379 | '(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|' + // ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) 380 | '([0-9a-fA-F]{1,4}:){1,4}:' + 381 | '((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}' + 382 | '(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])' + // 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address) 383 | ')' 384 | , 'ig' 385 | ) 386 | ]; 387 | 388 | 389 | /** 390 | * Extract IP-addresses from raw text. 391 | * 392 | * @param {string} str String to extract IP-addresses from. 393 | * @return {Promise} Promise where then() takes function with array of 394 | * IP-addresses as strings. 395 | */ 396 | const extractIP = str => { 397 | return new Promise((resolve, reject) => { 398 | // list of IP addresses 399 | let ipList = new Set(); 400 | 401 | /** 402 | * ******************************************************************* * 403 | * INTERNAL FUNCTION * 404 | * ******************************************************************* * 405 | * 406 | * Function to execute every event loop. Need to avoid blocking. 407 | * 408 | * @param object options Object with list of RegExp to exec and index of 409 | * RegExp to execute within given list: 410 | * { 411 | * re : [ RegExp ], 412 | * index: 0 413 | * } 414 | */ 415 | const fn = options => { 416 | // execute RegExp 417 | let match = options.re[options.index].exec(str); 418 | 419 | // match found 420 | if (match) { 421 | // add found IP 422 | ipList.add(match[0]); 423 | 424 | // next iteration 425 | setImmediate(fn, options); 426 | 427 | // no match, next iteration or end of loop 428 | } else { 429 | // try next RegExp from options.re array 430 | if (options.re.length > ++options.index) { 431 | setImmediate(fn, options); 432 | 433 | // no more RegExp in options.re array 434 | } else { 435 | resolve([...ipList]); 436 | } 437 | } 438 | }; 439 | 440 | // start loop 441 | setImmediate(fn, {re: EXTRACT_IP_REGEXP, index: 0}); 442 | }); 443 | }; 444 | 445 | 446 | /** 447 | * Initialize script to get GeoIP information. 448 | * 449 | * @param {string} dbPath Path to GeoIP files. 450 | * @return {Object} Promise. 451 | */ 452 | const geoInit = async (dbPath) => { 453 | const MAXMIND = 'GeoLite2-City.mmdb'; 454 | const MAXMIND_ASN = 'GeoLite2-ASN.mmdb'; 455 | const IP2LOCATON = 'IP2LOCATION-LITE-DB5.IPV6.BIN'; 456 | const IP2LOCATON_PROXY = 'IP2PROXY-LITE-PX4.BIN'; 457 | 458 | // do not initialize library twice, instantly resolve instead 459 | if (geoInitialized) { 460 | return; 461 | } 462 | 463 | // init ip2location and ip2location Proxy 464 | ip2loc.IP2Location_init(path.join(dbPath, IP2LOCATON)); 465 | ip2proxy.Open( path.join(dbPath, IP2LOCATON_PROXY)); 466 | 467 | // init MaxMind 468 | [maxMind, maxMind_asn] = await Promise.all([ 469 | mmdbReader.open(path.join(dbPath, MAXMIND)), 470 | mmdbReader.open(path.join(dbPath, MAXMIND_ASN)) 471 | ]); 472 | 473 | return; 474 | }; 475 | 476 | 477 | /** 478 | * Get GeoIP information. 479 | * 480 | * @param {string} host IP address to get info about. 481 | * @return {Promise} Promise Promise where then() has object as parameter like 482 | * in this example: 483 | * { 484 | * ip : '78.46.112.219', 485 | * asn : '24940', 486 | * as_org : 'Hetzner Online GmbH', 487 | * proxy : 'VPN', // see https://www.ip2proxy.com/ for possible values 488 | * country_code: ['DE'], 489 | * country : ['Germany'], 490 | * region : [ 'Bayern', 'Bavaria', 'Sachsen' ], 491 | * city : [ 'Nürnberg', 'Nuremberg', 'Falkenstein' ], 492 | * country_ru : 'Германия', 493 | * region_ru : 'Бавария', 494 | * city_ru : 'Нюрнберг', 495 | * timezone : 'Europe/Berlin', 496 | * coords : [ 497 | * { lat: 49.4478, lon: 11.068299999999994 }, 498 | * { lat: 49.4478, lon: 11.0683 } 499 | * ] 500 | * } 501 | */ 502 | const geoInfo = async host => { 503 | // not IP address 504 | if (!isIP(host)) { 505 | return void reject(new Error(`Not IP address: ${host}`)); 506 | } 507 | 508 | // perform requests to all DB 509 | const data = await Promise.all([ 510 | maxMind.city.bind(maxMind), 511 | ip2loc.IP2Location_get_all, 512 | maxMind_asn.asn.bind(maxMind_asn), 513 | ip2proxy.getAll 514 | ].map(fn => { try { return fn(host); } catch (err) { return null; } })); 515 | 516 | // result 517 | const result = { 518 | ip : host, 519 | asn : null, 520 | as_org : null, 521 | proxy : null, 522 | country_code: new Set(), 523 | country : new Set(), 524 | country_ru : null, 525 | region : new Set(), 526 | region_ru : null, 527 | city : new Set(), 528 | city_ru : null, 529 | timezone : null, 530 | coords : [] 531 | }; 532 | 533 | // fill from 'MaxMind' DB 534 | if (data[0] !== null) { 535 | // country 536 | if ('country' in data[0]) { 537 | if ('names' in data[0].country) { 538 | result.country.add(data[0].country.names.en); 539 | result.country_ru = data[0].country.names.ru; 540 | } 541 | 542 | if ('isoCode' in data[0].country) { 543 | result.country_code.add(data[0].country.isoCode ); 544 | } 545 | } 546 | 547 | // region 548 | if (Array.isArray(data[0].subdivisions) && data[0].subdivisions.length > 0) { 549 | result.region.add(data[0].subdivisions[0].names.en); 550 | 551 | if (typeof data[0].subdivisions[0].names.ru !== 'undefined') { 552 | result.region_ru = data[0].subdivisions[0].names.ru; 553 | } 554 | } 555 | 556 | // city 557 | if (('city' in data[0]) && ('names' in data[0].city)) { 558 | result.city.add(data[0].city.names.en); 559 | 560 | if (typeof data[0].city.names.ru !== 'undefined') 561 | result.city_ru = data[0].city.names.ru; 562 | } 563 | 564 | // coords 565 | result.coords.push({ 566 | lat: data[0].location.latitude, 567 | lon: data[0].location.longitude 568 | }); 569 | 570 | // timezone 571 | if (typeof data[0].location.time_zone !== 'undefined') { 572 | result.timezone = data[0].location.timeZone; 573 | } 574 | } 575 | 576 | // fill from 'IP2Location' DB 577 | if (data[1] !== null) { 578 | result.country.add( data[1].country_long ); 579 | result.country_code.add(data[1].country_short); 580 | result.region.add( data[1].region ); 581 | result.city.add( data[1].city ); 582 | 583 | if (data[1].latitude !== 0 && data[1].longitude !== 0) { 584 | result.coords.push({ 585 | lat: data[1].latitude, 586 | lon: data[1].longitude 587 | }); 588 | } 589 | } 590 | 591 | // fill from 'MaxMind ASN' DB 592 | if (data[2] !== null) { 593 | result.asn = data[2].autonomousSystemNumber ; 594 | result.as_org = data[2].autonomousSystemOrganization; 595 | } 596 | 597 | // fill from 'IP2Location Proxy' DB 598 | if (data[3] !== null && data[3].Proxy_Type.length > 1) { 599 | result.proxy = data[3].Proxy_Type; 600 | } 601 | 602 | // convert Sets to Arrays 603 | result.country_code = [...result.country_code]; 604 | result.country = [...result.country ]; 605 | result.region = [...result.region ]; 606 | result.city = [...result.city ]; 607 | 608 | 609 | return result; 610 | }; 611 | 612 | 613 | /** 614 | * Update MaxMind and IP2Location databases. 615 | * 616 | * @param {string} dbPath Full local path to store DB files 617 | * @param {string} token API token to download IP2Location database 618 | */ 619 | const geoUpdate = async (dbPath, token) => { 620 | const MAXMIND = { 621 | URL : 'https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz', 622 | FILENAME : path.join(dbPath, 'GeoLite2-City.tar.gz'), 623 | BASENAME : path.join(dbPath, 'GeoLite2-City.mmdb'), 624 | COMPRESSION: 'gzip' 625 | }; 626 | 627 | const MAXMIND_ASN = { 628 | URL : 'https://geolite.maxmind.com/download/geoip/database/GeoLite2-ASN.tar.gz', 629 | FILENAME : path.join(dbPath, 'GeoLite2-ASN.tar.gz'), 630 | BASENAME : path.join(dbPath, 'GeoLite2-ASN.mmdb'), 631 | COMPRESSION: 'gzip' 632 | }; 633 | 634 | const IP2LOCATON = { 635 | URL : `https://www.ip2location.com/download?file=DB5LITEBINIPV6&token=${token}`, 636 | FILENAME : path.join(dbPath, 'IP2LOCATION-LITE-DB5.IPV6.ZIP'), 637 | BASENAME : path.join(dbPath, 'IP2LOCATION-LITE-DB5.IPV6.BIN'), 638 | COMPRESSION: 'zip' 639 | }; 640 | 641 | const IP2LOCATON_PROXY = { 642 | URL : `https://www.ip2location.com/download?file=PX4LITEBIN&token=${token}`, 643 | FILENAME : path.join(dbPath, 'IP2PROXY-LITE-PX4.BIN.ZIP'), 644 | BASENAME : path.join(dbPath, 'IP2PROXY-LITE-PX4.BIN'), 645 | COMPRESSION: 'zip' 646 | }; 647 | 648 | const promises = [MAXMIND, MAXMIND_ASN, IP2LOCATON, IP2LOCATON_PROXY] 649 | .map(DB => new Promise((resolve, reject) => { 650 | https.get(DB.URL, res => { 651 | res.pipe(fs.createWriteStream(DB.FILENAME)); 652 | 653 | res.on('end', async () => { 654 | if (DB.COMPRESSION === 'zip') { 655 | const buf = await fs.promises.readFile(DB.FILENAME); 656 | const zip = await jszip.loadAsync(buf); 657 | zip.forEach((name, file) => { 658 | if (DB.BASENAME.includes(name)) { 659 | file.nodeStream() 660 | .pipe(fs.createWriteStream(DB.BASENAME)) 661 | .on('error', reject) 662 | .on('finish', resolve); 663 | } 664 | }); 665 | } else if (DB.COMPRESSION === 'gzip') { 666 | const inp = fs.createReadStream(DB.FILENAME); 667 | const out = fs.createWriteStream(DB.BASENAME); 668 | 669 | const gunzip = zlib.createGunzip(); 670 | 671 | const extract = tar.extract(); 672 | extract.on('entry', (header, stream, next) => { 673 | const filename = header.name.split('/')[1]; 674 | 675 | if((filename !== '') && DB.BASENAME.includes(filename)) { 676 | stream.pipe(out); 677 | } 678 | 679 | stream.on('end', next); 680 | stream.resume(); 681 | }); 682 | 683 | inp.pipe(gunzip).pipe(extract) 684 | .on('error', reject) 685 | .on('finish', resolve); 686 | } 687 | }); 688 | }) 689 | .on('error', reject); 690 | })); 691 | 692 | await Promise.all(promises); 693 | }; 694 | 695 | 696 | /** 697 | * Get BGP information, such as Autonomous System number. You can get this info 698 | * manually by using this command in Linux console: 699 | * $ dig +short 224.135.219.83.origin.asn.cymru.com. TXT 700 | * $ dig +short AS31999.asn.cymru.com. TXT 701 | * 702 | * @param {string} host IP address to get info about. 703 | * @return {Promise} Promise. 704 | */ 705 | const bgpInfo = async host => { 706 | const ip = host.trim(); 707 | 708 | // special servers to check BGP info 709 | const bgpServer4 = 'origin.asn.cymru.com'; 710 | const bgpServer6 = 'origin6.asn.cymru.com'; 711 | const bgpAsServer = 'asn.cymru.com'; 712 | 713 | let bgpRequestHost; 714 | 715 | if (net.isIPv4(ip)) { 716 | bgpRequestHost = ipReverse(ip).concat('.', bgpServer4); 717 | 718 | } else if (net.isIPv6(ip)) { 719 | bgpRequestHost = ipReverse(ip).concat('.', bgpServer6); 720 | 721 | // not IP 722 | } else { 723 | return reject(new Error(`Not valid IPv4: ${ip}`)); 724 | } 725 | 726 | const bgpObject = { 727 | 'as' : null, 728 | 'ip' : ip, 729 | 'prefix' : null, 730 | 'country_code' : null, 731 | 'registry' : null, 732 | 'allocation_date': null, 733 | 'name' : null 734 | } 735 | const result = []; 736 | 737 | // get 'AS', 'BGP Prefix', 'CC', 'Registry', 'Allocated' 738 | const response = await dns.resolve(bgpRequestHost, 'TXT'); 739 | 740 | // empty answer 741 | if ( 742 | (response === null) || 743 | !(0 in response) || 744 | !(0 in response[0]) 745 | ) { 746 | return null; 747 | } 748 | 749 | // construct result 750 | let response_arr = response[0][0].split(' | '); 751 | const as_nums = response_arr[0].split(' '); 752 | bgpObject.prefix = response_arr[1]; 753 | bgpObject.country_code = response_arr[2]; 754 | bgpObject.registry = response_arr[3]; 755 | bgpObject.allocation_date = response_arr[4]; 756 | 757 | // get 'AS Name' 758 | for (const as of as_nums) { 759 | const bgp = {...bgpObject}; 760 | bgp.as = as; 761 | 762 | bgpRequestHost = `AS${as}.${bgpAsServer}`; 763 | const response = await dns.resolve(bgpRequestHost, 'TXT'); 764 | if ( 765 | (response !== null) && 766 | (0 in response) && 767 | (0 in response[0]) 768 | ) { 769 | response_arr = response[0][0].split(' | '); 770 | bgp.name = response_arr[4]; 771 | } 772 | 773 | result.push(bgp); 774 | } 775 | 776 | return result; 777 | }; 778 | 779 | 780 | /** 781 | * Try to get all possible info about domain name or IP-address. 782 | * 783 | * @param {string} host Domain name or IP-address. 784 | * @return {object} Promise where `then()` has the following object as parameter: 785 | * { 786 | * host : host, 787 | * isIP : true, 788 | * longIP : null, 789 | * reverse : null, 790 | * geoInfo : null, 791 | * torInfo : null, 792 | * bgpInfo : null, 793 | * isDomain: false, 794 | * nslookup: null, 795 | * whois : null 796 | * } 797 | */ 798 | const hostInfo = async host => { 799 | // result skeleton 800 | const result = { 801 | host : host, 802 | isIP : true, 803 | longIP : null, 804 | reverse: null, 805 | geoInfo: null, 806 | torInfo: null, 807 | bgpInfo: null, 808 | 809 | isDomain: false, 810 | nslookup: null, 811 | 812 | whois: null 813 | }; 814 | 815 | // collect available info about IP 816 | if (isIP(host)) { 817 | result.isIP = true; 818 | result.isDomain = false; 819 | result.longIP = ip2long(host); 820 | 821 | const info = await Promise.all( 822 | [dns.reverse, geoInfo, torInfo, bgpInfo, whois] 823 | .map(promise => promise(host)) 824 | .map(promise => promise.catch(err => null)) 825 | ); 826 | 827 | result.reverse = info[0]; 828 | result.geoInfo = info[1]; 829 | result.torInfo = info[2]; 830 | result.bgpInfo = info[3]; 831 | result.whois = info[4]; 832 | 833 | // collect available info about domain 834 | } else { 835 | result.isIP = false; 836 | result.isDomain = true; 837 | 838 | const info = await Promise.all( 839 | [nslookup, whois] 840 | .map(promise => promise(host)) 841 | .map(promise => promise.catch(err => null)) 842 | ); 843 | 844 | result.nslookup = info[0]; 845 | result.whois = info[1]; 846 | } 847 | 848 | return result; 849 | }; 850 | 851 | 852 | exports.ip2long = ip2long; 853 | exports.isIP = isIP; 854 | exports.isDomain = isDomain; 855 | exports.ipReverse = ipReverse; 856 | exports.reverse = dns.reverse; 857 | exports.nslookup = nslookup; 858 | exports.whois = whois; 859 | exports.torInfo = torInfo; 860 | exports.extractIP = extractIP; 861 | exports.geoInit = geoInit; 862 | exports.geoInfo = geoInfo; 863 | exports.geoUpdate = geoUpdate; 864 | exports.bgpInfo = bgpInfo; 865 | exports.info = hostInfo; 866 | --------------------------------------------------------------------------------