ソースを参照

Feat: implement `SRC-IP`, `SRC-PORT`, `DEST-PORT`

SukkaW 1 年間 前
コミット
c71ed5a9d3
4 ファイル変更58 行追加59 行削除
  1. 0 29
      Build/lib/clash.ts
  2. 13 3
      Build/lib/rules/base.ts
  3. 45 1
      Build/lib/rules/ruleset.ts
  4. 0 26
      Build/lib/singbox.ts

+ 0 - 29
Build/lib/clash.ts

@@ -1,36 +1,7 @@
-import { domainWildCardToRegex, identity } from './misc';
-import { isProbablyIpv4, isProbablyIpv6 } from './is-fast-ip';
-
 const unsupported = Symbol('unsupported');
 const unsupported = Symbol('unsupported');
 
 
 // https://dreamacro.github.io/clash/configuration/rules.html
 // https://dreamacro.github.io/clash/configuration/rules.html
 export const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => string) | typeof unsupported> = {
 export const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => string) | typeof unsupported> = {
-  DOMAIN: identity,
-  'DOMAIN-SUFFIX': identity,
-  'DOMAIN-KEYWORD': identity,
-  'DOMAIN-WILDCARD': (_raw, _type, value) => `DOMAIN-REGEX,${domainWildCardToRegex(value)}`,
-  GEOIP: identity,
-  'IP-CIDR': identity,
-  'IP-CIDR6': identity,
-  'IP-ASN': identity,
-  'SRC-IP': (_raw, _type, value) => {
-    if (value.includes('/')) {
-      return `SRC-IP-CIDR,${value}`;
-    }
-    if (isProbablyIpv4(value)) {
-      return `SRC-IP-CIDR,${value}/32`;
-    }
-    if (isProbablyIpv6(value)) {
-      return `SRC-IP-CIDR6,${value}/128`;
-    }
-    return '';
-  },
-  'SRC-IP-CIDR': identity,
-  'SRC-PORT': identity,
-  'DST-PORT': identity,
-  'PROCESS-NAME': (_raw, _type, value) => ((value.includes('/') || value.includes('\\')) ? `PROCESS-PATH,${value}` : `PROCESS-NAME,${value}`),
-  'DEST-PORT': (_raw, _type, value) => `DST-PORT,${value}`,
-  'IN-PORT': (_raw, _type, value) => `SRC-PORT,${value}`,
   'URL-REGEX': unsupported,
   'URL-REGEX': unsupported,
   'USER-AGENT': unsupported
   'USER-AGENT': unsupported
 };
 };

+ 13 - 3
Build/lib/rules/base.ts

@@ -27,9 +27,10 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
   protected ipcidr6NoResolve = new Set<string>();
   protected ipcidr6NoResolve = new Set<string>();
   protected geoip = new Set<string>();
   protected geoip = new Set<string>();
   protected groipNoResolve = new Set<string>();
   protected groipNoResolve = new Set<string>();
-  // TODO: add sourceIpcidr
-  // TODO: add sourcePort
-  // TODO: add port
+
+  protected sourceIpOrCidr = new Set<string>();
+  protected sourcePort = new Set<string>();
+  protected destPort = new Set<string>();
 
 
   protected otherRules: string[] = [];
   protected otherRules: string[] = [];
   protected abstract type: 'domainset' | 'non_ip' | 'ip';
   protected abstract type: 'domainset' | 'non_ip' | 'ip';
@@ -188,6 +189,15 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
         case 'GEOIP':
         case 'GEOIP':
           (arg === 'no-resolve' ? this.groipNoResolve : this.geoip).add(value);
           (arg === 'no-resolve' ? this.groipNoResolve : this.geoip).add(value);
           break;
           break;
+        case 'SRC-IP':
+          this.sourceIpOrCidr.add(value);
+          break;
+        case 'SRC-PORT':
+          this.sourcePort.add(value);
+          break;
+        case 'DEST-PORT':
+          this.destPort.add(value);
+          break;
         default:
         default:
           this.otherRules.push(line);
           this.otherRules.push(line);
           break;
           break;

+ 45 - 1
Build/lib/rules/ruleset.ts

@@ -8,7 +8,7 @@ import { sortDomains } from '../stable-sort-domain';
 import { RuleOutput } from './base';
 import { RuleOutput } from './base';
 import picocolors from 'picocolors';
 import picocolors from 'picocolors';
 import { normalizeDomain } from '../normalize-domain';
 import { normalizeDomain } from '../normalize-domain';
-import { isProbablyIpv4 } from '../is-fast-ip';
+import { isProbablyIpv4, isProbablyIpv6 } from '../is-fast-ip';
 
 
 type Preprocessed = [domain: string[], domainSuffix: string[], sortedDomainRules: string[]];
 type Preprocessed = [domain: string[], domainSuffix: string[], sortedDomainRules: string[]];
 
 
@@ -52,6 +52,10 @@ export class RulesetOutput extends RuleOutput<Preprocessed> {
     appendArrayFromSet(results, this.processName, i => `PROCESS-NAME,${i}`);
     appendArrayFromSet(results, this.processName, i => `PROCESS-NAME,${i}`);
     appendArrayFromSet(results, this.processPath, i => `PROCESS-NAME,${i}`);
     appendArrayFromSet(results, this.processPath, i => `PROCESS-NAME,${i}`);
 
 
+    appendArrayFromSet(results, this.sourceIpOrCidr, i => `SRC-IP,${i}`);
+    appendArrayFromSet(results, this.sourcePort, i => `SRC-PORT,${i}`);
+    appendArrayFromSet(results, this.destPort, i => `DEST-PORT,${i}`);
+
     appendArrayInPlace(results, this.otherRules);
     appendArrayInPlace(results, this.otherRules);
 
 
     appendArrayFromSet(results, this.urlRegex, i => `URL-REGEX,${i}`);
     appendArrayFromSet(results, this.urlRegex, i => `URL-REGEX,${i}`);
@@ -86,6 +90,21 @@ export class RulesetOutput extends RuleOutput<Preprocessed> {
     appendArrayFromSet(results, this.processName, i => `PROCESS-NAME,${i}`);
     appendArrayFromSet(results, this.processName, i => `PROCESS-NAME,${i}`);
     appendArrayFromSet(results, this.processPath, i => `PROCESS-PATH,${i}`);
     appendArrayFromSet(results, this.processPath, i => `PROCESS-PATH,${i}`);
 
 
+    appendArrayFromSet(results, this.sourceIpOrCidr, value => {
+      if (value.includes('/')) {
+        return `SRC-IP-CIDR,${value}`;
+      }
+      if (isProbablyIpv4(value)) {
+        return `SRC-IP-CIDR,${value}/32`;
+      }
+      if (isProbablyIpv6(value)) {
+        return `SRC-IP-CIDR6,${value}/128`;
+      }
+      return '';
+    });
+    appendArrayFromSet(results, this.sourcePort, i => `SRC-PORT,${i}`);
+    appendArrayFromSet(results, this.destPort, i => `DST-PORT,${i}`);
+
     // appendArrayInPlace(results, this.otherRules);
     // appendArrayInPlace(results, this.otherRules);
 
 
     appendArrayInPlace(
     appendArrayInPlace(
@@ -127,6 +146,31 @@ export class RulesetOutput extends RuleOutput<Preprocessed> {
         domain_keyword: Array.from(this.domainKeywords),
         domain_keyword: Array.from(this.domainKeywords),
         domain_regex: Array.from(this.domainWildcard).map(RuleOutput.domainWildCardToRegex),
         domain_regex: Array.from(this.domainWildcard).map(RuleOutput.domainWildCardToRegex),
         ip_cidr,
         ip_cidr,
+        source_ip_cidr: [...this.sourceIpOrCidr].reduce<string[]>((acc, cur) => {
+          if (cur.includes('/')) {
+            acc.push(cur);
+          } else if (isProbablyIpv4(cur)) {
+            acc.push(cur + '/32');
+          } else if (isProbablyIpv6(cur)) {
+            acc.push(cur + '/128');
+          }
+
+          return acc;
+        }, []),
+        source_port: [...this.sourcePort].reduce<number[]>((acc, cur) => {
+          const tmp = Number(cur);
+          if (!Number.isNaN(tmp)) {
+            acc.push(tmp);
+          }
+          return acc;
+        }, []),
+        port: [...this.destPort].reduce<number[]>((acc, cur) => {
+          const tmp = Number(cur);
+          if (!Number.isNaN(tmp)) {
+            acc.push(tmp);
+          }
+          return acc;
+        }, []),
         process_name: Array.from(this.processName),
         process_name: Array.from(this.processName),
         process_path: Array.from(this.processPath)
         process_path: Array.from(this.processPath)
       }]
       }]

+ 0 - 26
Build/lib/singbox.ts

@@ -1,34 +1,8 @@
-import { isProbablyIpv4, isProbablyIpv6 } from './is-fast-ip';
-
 const unsupported = Symbol('unsupported');
 const unsupported = Symbol('unsupported');
 
 
-function toNumberTuple<T extends string>(key: T, value: string): [T, number] | null {
-  const tmp = Number(value);
-  return Number.isNaN(tmp) ? null : [key, tmp];
-}
-
 // https://sing-box.sagernet.org/configuration/rule-set/source-format/
 // https://sing-box.sagernet.org/configuration/rule-set/source-format/
 export const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => [key: keyof SingboxHeadlessRule, value: Required<SingboxHeadlessRule>[keyof SingboxHeadlessRule][number]] | null) | typeof unsupported> = {
 export const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => [key: keyof SingboxHeadlessRule, value: Required<SingboxHeadlessRule>[keyof SingboxHeadlessRule][number]] | null) | typeof unsupported> = {
   'IP-ASN': unsupported,
   'IP-ASN': unsupported,
-  'SRC-IP': (_1, _2, value) => {
-    if (value.includes('/')) {
-      return ['source_ip_cidr', value];
-    }
-    if (isProbablyIpv4(value)) {
-      return ['source_ip_cidr', value + '/32'];
-    }
-    if (isProbablyIpv6(value)) {
-      return ['source_ip_cidr', value + '/128'];
-    }
-    return null;
-  },
-  'SRC-IP-CIDR': (_1, _2, value) => ['source_ip_cidr', value.endsWith(',no-resolve') ? value.slice(0, -11) : value],
-  'SRC-PORT': (_1, _2, value) => toNumberTuple('source_port', value),
-  'DST-PORT': (_1, _2, value) => toNumberTuple('port', value),
-  'PROCESS-NAME': (_1, _2, value) => ((value.includes('/') || value.includes('\\')) ? ['process_path', value] : ['process_name', value]),
-  // 'PROCESS-PATH': (_1, _2, value) => ['process_path', value],
-  'DEST-PORT': (_1, _2, value) => toNumberTuple('port', value),
-  'IN-PORT': (_1, _2, value) => toNumberTuple('source_port', value),
   'URL-REGEX': unsupported,
   'URL-REGEX': unsupported,
   'USER-AGENT': unsupported
   'USER-AGENT': unsupported
 };
 };