Browse Source

Make sure chnroutes not to be invalid

SukkaW 1 year ago
parent
commit
1caf83b261
3 changed files with 77 additions and 16 deletions
  1. 26 9
      Build/build-chn-cidr.ts
  2. 4 1
      Build/constants/cidr.ts
  3. 47 6
      Build/lib/fs-memo.ts

+ 26 - 9
Build/build-chn-cidr.ts

@@ -2,21 +2,38 @@ import { fetchRemoteTextByLine } from './lib/fetch-text-by-line';
 import { processLineFromReadline } from './lib/process-line';
 import { processLineFromReadline } from './lib/process-line';
 import { task } from './trace';
 import { task } from './trace';
 
 
-import { exclude } from 'fast-cidr-tools';
+import { contains, exclude } from 'fast-cidr-tools';
 import { createMemoizedPromise } from './lib/memo-promise';
 import { createMemoizedPromise } from './lib/memo-promise';
 import { CN_CIDR_NOT_INCLUDED_IN_CHNROUTE, NON_CN_CIDR_INCLUDED_IN_CHNROUTE } from './constants/cidr';
 import { CN_CIDR_NOT_INCLUDED_IN_CHNROUTE, NON_CN_CIDR_INCLUDED_IN_CHNROUTE } from './constants/cidr';
 import { appendArrayInPlace } from './lib/append-array-in-place';
 import { appendArrayInPlace } from './lib/append-array-in-place';
 import { IPListOutput } from './lib/create-file';
 import { IPListOutput } from './lib/create-file';
+import { cachedOnlyFail } from './lib/fs-memo';
 
 
-export const getChnCidrPromise = createMemoizedPromise(async () => {
-  const [cidr4, cidr6] = await Promise.all([
-    fetchRemoteTextByLine('https://raw.githubusercontent.com/misakaio/chnroutes2/master/chnroutes.txt').then(processLineFromReadline),
-    fetchRemoteTextByLine('https://gaoyifan.github.io/china-operator-ip/china6.txt').then(processLineFromReadline)
-  ]);
+const PROBE_CHN_CIDR = [
+  // NetEase Hangzhou
+  '223.252.196.38'
+];
 
 
-  appendArrayInPlace(cidr4, CN_CIDR_NOT_INCLUDED_IN_CHNROUTE);
-  return [exclude(cidr4, NON_CN_CIDR_INCLUDED_IN_CHNROUTE, true), cidr6] as const;
-});
+export const getChnCidrPromise = createMemoizedPromise(cachedOnlyFail(
+  async function getChnCidr() {
+    const [cidr4, cidr6] = await Promise.all([
+      fetchRemoteTextByLine('https://raw.githubusercontent.com/misakaio/chnroutes2/master/chnroutes.txt').then(processLineFromReadline),
+      fetchRemoteTextByLine('https://gaoyifan.github.io/china-operator-ip/china6.txt').then(processLineFromReadline)
+    ]);
+
+    appendArrayInPlace(cidr4, CN_CIDR_NOT_INCLUDED_IN_CHNROUTE);
+
+    if (!contains(cidr4, PROBE_CHN_CIDR)) {
+      throw new TypeError('chnroutes missing probe IP');
+    }
+
+    return [exclude(cidr4, NON_CN_CIDR_INCLUDED_IN_CHNROUTE, true), cidr6] as const;
+  },
+  {
+    serializer: JSON.stringify,
+    deserializer: JSON.parse
+  }
+));
 
 
 export const buildChnCidr = task(require.main === module, __filename)(async (span) => {
 export const buildChnCidr = task(require.main === module, __filename)(async (span) => {
   const [filteredCidr4, cidr6] = await span.traceChildAsync('download chnroutes2', getChnCidrPromise);
   const [filteredCidr4, cidr6] = await span.traceChildAsync('download chnroutes2', getChnCidrPromise);

+ 4 - 1
Build/constants/cidr.ts

@@ -55,5 +55,8 @@ export const CN_CIDR_NOT_INCLUDED_IN_CHNROUTE = [
   '139.219.0.0/16', // AS58593, Azure China, Shanghai
   '139.219.0.0/16', // AS58593, Azure China, Shanghai
   '143.64.0.0/16', // AS58593, Azure China, Beijing
   '143.64.0.0/16', // AS58593, Azure China, Beijing
   '159.27.0.0/16', // AS58593, Azure China, Beijing
   '159.27.0.0/16', // AS58593, Azure China, Beijing
-  '163.228.0.0/16' // AS58593, Azure China, Beijing
+  '163.228.0.0/16', // AS58593, Azure China, Beijing
+
+  // NetEase
+  '223.252.196.0/24'
 ];
 ];

+ 47 - 6
Build/lib/fs-memo.ts

@@ -62,9 +62,7 @@ export function cache<Args extends TypesonValue[], T>(
 
 
     const cacheName = fn.name || fixedKey;
     const cacheName = fn.name || fixedKey;
 
 
-    const { temporaryBypass, incrementTtlWhenHit } = opt;
-
-    if (temporaryBypass) {
+    if (opt.temporaryBypass) {
       return fn(...args);
       return fn(...args);
     }
     }
 
 
@@ -82,11 +80,54 @@ export function cache<Args extends TypesonValue[], T>(
 
 
     console.log(picocolors.green('[cache] hit'), picocolors.gray(cacheName || cacheKey));
     console.log(picocolors.green('[cache] hit'), picocolors.gray(cacheName || cacheKey));
 
 
-    if (incrementTtlWhenHit) {
-      fsMemoCache.updateTtl(cacheKey, TTL);
-    }
+    fsMemoCache.updateTtl(cacheKey, TTL);
 
 
     const deserializer = 'deserializer' in opt ? opt.deserializer : identity as any;
     const deserializer = 'deserializer' in opt ? opt.deserializer : identity as any;
     return deserializer(cached);
     return deserializer(cached);
   };
   };
 }
 }
+
+export function cachedOnlyFail<Args extends TypesonValue[], T>(
+  fn: (...args: Args) => Promise<T>,
+  opt: FsMemoCacheOptions<T>
+): (...args: Args) => Promise<T> {
+  const fixedKey = fn.toString();
+
+  return async function cachedCb(...args: Args) {
+    // Construct the complete cache key for this function invocation
+    // typeson.stringify is still limited. For now we uses typescript to guard the args.
+    const cacheKey = (await Promise.all([
+      xxhash64(fixedKey),
+      xxhash64(typeson.stringifySync(args))
+    ])).join('|');
+
+    const cacheName = fn.name || fixedKey;
+
+    if (opt.temporaryBypass) {
+      return fn(...args);
+    }
+
+    const cached = fsMemoCache.get(cacheKey);
+
+    try {
+      const value = await fn(...args);
+
+      const serializer = 'serializer' in opt ? opt.serializer : identity as any;
+      fsMemoCache.set(cacheKey, serializer(value), TTL);
+
+      return value;
+    } catch (e) {
+      if (cached == null) {
+        console.log(picocolors.red('[fail] and no cache, throwing'), picocolors.gray(cacheName || cacheKey));
+        throw e;
+      }
+
+      fsMemoCache.updateTtl(cacheKey, TTL);
+
+      console.log(picocolors.yellow('[fail] try cache'), picocolors.gray(cacheName || cacheKey));
+      const deserializer = 'deserializer' in opt ? opt.deserializer : identity as any;
+
+      return deserializer(cached);
+    }
+  };
+}