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;
|