const { promisify } = require('util'); const dgram = require('dgram'); const { isIPv6, isIPv4 } = require('net'); const dns = require('dns-socket'); const HttpClient = require('../httpclient'); const emptyIP = ''; const defaults = { timeout: 3000, type: 'http', // 'http' | 'dns' | 'all' }; const dnsServers = [ { v4: { servers: [ '208.67.222.222', '208.67.220.220', '208.67.222.220', '208.67.220.222', ], name: 'myip.opendns.com', type: 'A', }, v6: { servers: [ '2620:0:ccc::2', '2620:0:ccd::2', ], name: 'myip.opendns.com', type: 'AAAA', }, }, ]; const type = { v4: { dnsServers: dnsServers.map(({v4: {servers, ...question}}) => ({ servers, question, })), httpsUrls: [ 'https://icanhazip.com/', 'https://api.ipify.org/', ], }, v6: { dnsServers: dnsServers.map(({v6: {servers, ...question}}) => ({ servers, question, })), httpsUrls: [ 'https://icanhazip.com/', 'https://api6.ipify.org/', ], }, }; const queryDns = (version, options) => { const data = type[version]; const socket = dns({ retries: 0, maxQueries: 1, socket: dgram.createSocket(version === 'v6' ? 'udp6' : 'udp4'), timeout: options.timeout, }); const socketQuery = promisify(socket.query.bind(socket)); const promise = (async () => { for (const dnsServerInfo of data.dnsServers) { const {servers, question} = dnsServerInfo; for (const server of servers) { if (socket.destroyed) { return emptyIP; } try { const {name, type, transform} = question; const dnsResponse = await socketQuery({questions: [{name, type}]}, 53, server); const { answers: { 0: { data, }, }, } = dnsResponse; const response = (typeof data === 'string' ? data : data.toString()).trim(); const ip = (transform && version === 'v6') ? transform(response) : response; const method = version === 'v6' ? isIPv6 : isIPv4; if (ip && method(ip)) { socket.destroy(); return ip; } } catch (error) { // Log.coreLogger.error('[ee-core] [utils/ip] queryDns error:', error); } } } socket.destroy(); return emptyIP; })(); promise.cancel = () => { socket.destroy(); }; return promise; }; const queryHttps = (version, options) => { let cancel; const hc = new HttpClient(); const promise = (async () => { const requestOptions = { method: 'GET', timeout: options.timeout, dataType: 'text', }; const urls = [ ...type[version].httpsUrls, ...(options.fallbackUrls ?? []), ]; for (const url of urls) { try { const gotPromise = hc.request(url, requestOptions); gotPromise.cancel = () => { // todo } cancel = gotPromise.cancel; const response = await gotPromise; let result = response.status == 200 ? response.data : ''; const ip = result.trim(); const method = version === 'v6' ? isIPv6 : isIPv4; if (ip && method(ip)) { return ip; } } catch (error) { //Log.coreLogger.error('[ee-core] [utils/ip] queryHttps error:', error); } } return emptyIP; })(); promise.cancel = function () { return cancel.apply(this); }; return promise; }; const queryAll = (version, options) => { let cancel; const promise = (async () => { let response; const dnsPromise = queryDns(version, options); cancel = dnsPromise.cancel; try { response = await dnsPromise; } catch { const httpsPromise = queryHttps(version, options); cancel = httpsPromise.cancel; response = await httpsPromise; } return response; })(); promise.cancel = cancel; return promise; }; /** * 查询 public ipv4 */ function publicIpv4(options) { options = { ...defaults, ...options, }; if (options.type == 'http') { return queryHttps('v4', options); } if (options.type == 'dns') { return queryDns('v4', options); } return queryAll('v4', options); } /** * 查询public ipv6 */ function publicIpv6(options) { options = { ...defaults, ...options, }; if (options.type == 'http') { return queryHttps('v6', options); } if (options.type == 'dns') { return queryDns('v6', options); } return queryAll('v6', options); } /** * todo 未来趋势是ipv6优先,以后再放开 */ const publicIp = createPublicIp(publicIpv4, publicIpv6); function createPublicIp(publicIpv4, publicIpv6) { return function publicIp(options) { const ipv4Promise = publicIpv4(options); const ipv6Promise = publicIpv6(options); const promise = (async () => { try { const ipv6 = await ipv6Promise; ipv4Promise.cancel(); return ipv6; } catch (ipv6Error) { //Log.coreLogger.error('[ee-core] [utils/ip] publicIp ipv6Error:', ipv6Error); try { return await ipv4Promise; } catch (ipv4Error) { //Log.coreLogger.error('[ee-core] [utils/ip] publicIp ipv4Error:', ipv4Error); } } })(); promise.cancel = () => { ipv4Promise.cancel(); ipv6Promise.cancel(); }; return promise; }; } const IP = { //publicIp, publicIpv4, publicIpv6 } module.exports = IP;