From 0848d5a39e691b04e5367132ef74cafef262733b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Mon, 30 Dec 2024 23:59:09 +0800 Subject: [PATCH] feat: clouflare --- src/common/cloudflare.ts | 116 +++++++++++++++++++++++++++++++++++++++ tsconfig.json | 4 +- 2 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/common/cloudflare.ts diff --git a/src/common/cloudflare.ts b/src/common/cloudflare.ts new file mode 100644 index 00000000..95a3baf0 --- /dev/null +++ b/src/common/cloudflare.ts @@ -0,0 +1,116 @@ +import * as net from 'net'; +import * as https from 'https'; + +export const cloudflareIps = [ + '103.21.244.0/22', + '103.22.200.0/22', + '103.31.4.0/22', + '104.16.0.0/13', + '104.24.0.0/14', + '108.162.192.0/18', + '131.0.72.0/22', + '141.101.64.0/18', + '162.158.0.0/15', + '172.64.0.0/13', + '173.245.48.0/20', + '188.114.96.0/20', + '190.93.240.0/20', + '197.234.240.0/22', + '198.41.128.0/17' +]; + +// 将IP地址转换为整数 +const ipToInt = (ip: string): number => ip.split('.').reduce((acc, octet) => (acc << 8) + Number(octet), 0) >>> 0; + +// 将整数转换为IP地址 +const intToIp = (int: number): string => `${(int >>> 24) & 0xFF}.${(int >>> 16) & 0xFF}.${(int >>> 8) & 0xFF}.${int & 0xFF}`; + +// 解析CIDR范围 +const parseCIDR = (cidr: string): string[] => { + const [base, maskLength] = cidr.split('/'); + const baseIP = ipToInt(base); + return Array.from({ length: 1 << (32 - Number(maskLength)) }, (_, i) => intToIp(baseIP + i)) + .filter(ip => !ip.endsWith('.0')); // 排除以 .0 结尾的 IP 地址 +} + +// 解析IP地址和IP范围 +const parseIPs = (ipList: string[]): string[] => ipList.flatMap(ip => ip.includes('/') ? parseCIDR(ip) : net.isIP(ip) ? [ip] : []); + +// 检查URL的HTTP连接性 +const checkUrl = (ip: string, domain: string): Promise => new Promise((resolve) => { + const options = { + hostname: ip, + port: 443, + method: 'GET', + path: '/', + timeout: 2000, + headers: { + 'Host': domain, + "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.2128.93 Safari/537.36' + } + }; + const req = https.request(options, (res) => { + res.on('data', (data) => { + //console.log('[NetWork] [Scan] Checking Data! ', domain, ' --> ', ip, data.toString()); + }); + if (res.statusCode && res.statusCode > 0 && res.statusCode < 500) { + console.log('[NetWork] [Scan] Checking Connect! ', domain, ' --> ', ip); + resolve(true); + } else { + console.log('[NetWork] [Scan] Checking Failed! ', domain, ' --> ', ip, ' Status Code: ', res.statusCode); + resolve(false); + } + }); + req.on('error', () => { + console.log('[NetWork] [Scan] Checking Error! ', domain, ' --> ', ip); + resolve(false); + }); + req.on('timeout', () => { + req.destroy(); + console.log('[NetWork] [Scan] Checking Timeout! ', domain, ' --> ', ip); + resolve(false); + }); + req.end(); +}); + +// 检查特定域名的HTTP连接性 +const checkDomainConnectivity = async (ip: string, domains: string[]): Promise => { + const checkPromises = domains.map(domain => checkUrl(ip, domain)); + const results = await Promise.all(checkPromises); + return results.some(result => result); +} + +export const optimizeCloudflareIPs = async (ipList: string[], domains: string[]): Promise => { + if ((await Promise.all(domains.map(domain => checkUrl(domain, domain)))).every(result => result)) return undefined; + for (const ip of parseIPs(ipList)) { + if (await checkDomainConnectivity(ip, domains)) return ip; + } + return undefined; +} + +export const findRealHost = async () => { + const domains = ['umami.napneko.icu', 'rkey.napneko.icu']; + const ip = await optimizeCloudflareIPs(cloudflareIps, domains); + return { + 'umami.napneko.icu': ip ?? 'umami.napneko.icu', + 'rkey.napneko.icu': ip ?? 'rkey.napneko.icu' + }; +} + +type Cache = { + [key: string]: string; +}; + +let cache: Cache = { + 'cached': '', + 'umami.napneko.icu': 'umami.napneko.icu', + 'rkey.napneko.icu': 'rkey.napneko.icu' +}; + +export const getDomainIp = async (domain: string) => { + if (!cache.cached) { + cache = { cached: 'cached', ...(await findRealHost()) }; + } + console.log('[NetWork] [Cache] ', domain, ' --> ', cache[domain] ?? domain); + return cache[domain] ?? domain; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 2e018434..48537ae5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { - "target": "ES2020", + "target": "ES2021", "useDefineForClassFields": true, "module": "ESNext", "lib": [ - "ES2020", + "ES2021", "DOM", "DOM.Iterable" ],