mirror of
https://github.com/SukkaW/Surge.git
synced 2025-12-13 01:30:37 +08:00
Feat: sukka_local_dns_mapping.sgmodule now uses new rule-set syntax
This commit is contained in:
parent
8d5da9776a
commit
6858cd63b4
@ -10,7 +10,7 @@ import { SHARED_DESCRIPTION } from './constants/description';
|
|||||||
import { createMemoizedPromise } from './lib/memo-promise';
|
import { createMemoizedPromise } from './lib/memo-promise';
|
||||||
import * as yaml from 'yaml';
|
import * as yaml from 'yaml';
|
||||||
import { appendArrayInPlace } from './lib/append-array-in-place';
|
import { appendArrayInPlace } from './lib/append-array-in-place';
|
||||||
import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, SOURCE_DIR } from './constants/dir';
|
import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, OUTPUT_MODULES_RULES_DIR, SOURCE_DIR } from './constants/dir';
|
||||||
import { RulesetOutput } from './lib/create-file';
|
import { RulesetOutput } from './lib/create-file';
|
||||||
|
|
||||||
export function createGetDnsMappingRule(allowWildcard: boolean) {
|
export function createGetDnsMappingRule(allowWildcard: boolean) {
|
||||||
@ -78,7 +78,7 @@ export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(a
|
|||||||
export const buildDomesticRuleset = task(require.main === module, __filename)(async (span) => {
|
export const buildDomesticRuleset = task(require.main === module, __filename)(async (span) => {
|
||||||
const [domestics, directs, lans] = await getDomesticAndDirectDomainsRulesetPromise();
|
const [domestics, directs, lans] = await getDomesticAndDirectDomainsRulesetPromise();
|
||||||
|
|
||||||
const dataset: DNSMapping[] = ([DOH_BOOTSTRAP, DOMESTICS, DIRECTS] as const).flatMap(Object.values);
|
const dataset: Array<[name: string, DNSMapping]> = ([DOH_BOOTSTRAP, DOMESTICS, DIRECTS, LAN] as const).flatMap(Object.entries);
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
new RulesetOutput(span, 'domestic', 'non_ip')
|
new RulesetOutput(span, 'domestic', 'non_ip')
|
||||||
@ -108,6 +108,41 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
|
|||||||
])
|
])
|
||||||
.addFromRuleset(lans)
|
.addFromRuleset(lans)
|
||||||
.write(),
|
.write(),
|
||||||
|
|
||||||
|
...dataset.map(([name, { ruleset, domains }]) => {
|
||||||
|
if (!ruleset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = new RulesetOutput(span, name.toLowerCase(), 'sukka_local_dns_mapping').withTitle(`Sukka's Ruleset - Local DNS Mapping (${name})`).withDescription([
|
||||||
|
...SHARED_DESCRIPTION,
|
||||||
|
'',
|
||||||
|
'This is an internal rule that is only referenced by sukka_local_dns_mapping.sgmodule',
|
||||||
|
'Do not use this file in your Rule section, all rules are included in non_ip/domestic.conf already.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
domains.forEach((domain) => {
|
||||||
|
switch (domain[0]) {
|
||||||
|
case '$':
|
||||||
|
output.addDomain(domain.slice(1));
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
output.addDomainSuffix(domain.slice(1));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
output.addDomainSuffix(domain);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return output.write({
|
||||||
|
surge: true,
|
||||||
|
clash: false,
|
||||||
|
singbox: false,
|
||||||
|
surgeDir: OUTPUT_MODULES_RULES_DIR
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
compareAndWriteFile(
|
compareAndWriteFile(
|
||||||
span,
|
span,
|
||||||
[
|
[
|
||||||
@ -119,26 +154,31 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
|
|||||||
// I use an object to deduplicate the domains
|
// I use an object to deduplicate the domains
|
||||||
// Otherwise I could just construct an array directly
|
// Otherwise I could just construct an array directly
|
||||||
dataset.reduce<Record<string, string>>((acc, cur) => {
|
dataset.reduce<Record<string, string>>((acc, cur) => {
|
||||||
const { domains, dns, hosts } = cur;
|
const ruleset_name = cur[0].toLowerCase();
|
||||||
|
const { domains, dns, hosts, ruleset } = cur[1];
|
||||||
|
|
||||||
Object.entries(hosts).forEach(([dns, ips]) => {
|
Object.entries(hosts).forEach(([dns, ips]) => {
|
||||||
acc[dns] ||= ips.join(', ');
|
acc[dns] ||= ips.join(', ');
|
||||||
});
|
});
|
||||||
|
|
||||||
domains.forEach((domain) => {
|
if (ruleset) {
|
||||||
switch (domain[0]) {
|
acc[`RULE-SET:https://ruleset.skk.moe/Modules/Rules/sukka_local_dns_mapping/${ruleset_name}.conf`] ||= `server:${dns}`;
|
||||||
case '$':
|
} else {
|
||||||
acc[domain.slice(1)] ||= `server:${dns}`;
|
domains.forEach((domain) => {
|
||||||
break;
|
switch (domain[0]) {
|
||||||
case '+':
|
case '$':
|
||||||
acc[`*.${domain.slice(1)}`] ||= `server:${dns}`;
|
acc[domain.slice(1)] ||= `server:${dns}`;
|
||||||
break;
|
break;
|
||||||
default:
|
case '+':
|
||||||
acc[domain] ||= `server:${dns}`;
|
acc[`*.${domain.slice(1)}`] ||= `server:${dns}`;
|
||||||
acc[`*.${domain}`] ||= `server:${dns}`;
|
break;
|
||||||
break;
|
default:
|
||||||
}
|
acc[domain] ||= `server:${dns}`;
|
||||||
});
|
acc[`*.${domain}`] ||= `server:${dns}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
@ -153,7 +193,7 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
|
|||||||
dns: { 'nameserver-policy': Record<string, string | string[]> },
|
dns: { 'nameserver-policy': Record<string, string | string[]> },
|
||||||
hosts: Record<string, string>
|
hosts: Record<string, string>
|
||||||
}>((acc, cur) => {
|
}>((acc, cur) => {
|
||||||
const { domains, dns, ...rest } = cur;
|
const { domains, dns, ...rest } = cur[1];
|
||||||
domains.forEach((domain) => {
|
domains.forEach((domain) => {
|
||||||
let domainWildcard = domain;
|
let domainWildcard = domain;
|
||||||
if (domain[0] === '$') {
|
if (domain[0] === '$') {
|
||||||
|
|||||||
@ -11,5 +11,6 @@ export const OUTPUT_SURGE_DIR = path.join(PUBLIC_DIR, 'List');
|
|||||||
export const OUTPUT_CLASH_DIR = path.resolve(PUBLIC_DIR, 'Clash');
|
export const OUTPUT_CLASH_DIR = path.resolve(PUBLIC_DIR, 'Clash');
|
||||||
export const OUTPUT_SINGBOX_DIR = path.resolve(PUBLIC_DIR, 'sing-box');
|
export const OUTPUT_SINGBOX_DIR = path.resolve(PUBLIC_DIR, 'sing-box');
|
||||||
export const OUTPUT_MODULES_DIR = path.resolve(PUBLIC_DIR, 'Modules');
|
export const OUTPUT_MODULES_DIR = path.resolve(PUBLIC_DIR, 'Modules');
|
||||||
|
export const OUTPUT_MODULES_RULES_DIR = path.resolve(OUTPUT_MODULES_DIR, 'Rules');
|
||||||
export const OUTPUT_INTERNAL_DIR = path.resolve(PUBLIC_DIR, 'Internal');
|
export const OUTPUT_INTERNAL_DIR = path.resolve(PUBLIC_DIR, 'Internal');
|
||||||
export const OUTPUT_MOCK_DIR = path.resolve(PUBLIC_DIR, 'Mock');
|
export const OUTPUT_MOCK_DIR = path.resolve(PUBLIC_DIR, 'Mock');
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
|
|||||||
protected destPort = 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' | (string & {});
|
||||||
|
|
||||||
private pendingPromise: Promise<any> | null = null;
|
private pendingPromise: Promise<any> | null = null;
|
||||||
|
|
||||||
@ -295,13 +295,29 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
write(): Promise<void> {
|
write({
|
||||||
|
surge = true,
|
||||||
|
clash = true,
|
||||||
|
singbox = true,
|
||||||
|
surgeDir = OUTPUT_SURGE_DIR,
|
||||||
|
clashDir = OUTPUT_CLASH_DIR,
|
||||||
|
singboxDir = OUTPUT_SINGBOX_DIR
|
||||||
|
}: {
|
||||||
|
surge?: boolean,
|
||||||
|
clash?: boolean,
|
||||||
|
singbox?: boolean,
|
||||||
|
surgeDir?: string,
|
||||||
|
clashDir?: string,
|
||||||
|
singboxDir?: string
|
||||||
|
} = {}): Promise<void> {
|
||||||
return this.done().then(() => this.span.traceChildAsync('write all', async () => {
|
return this.done().then(() => this.span.traceChildAsync('write all', async () => {
|
||||||
invariant(this.title, 'Missing title');
|
invariant(this.title, 'Missing title');
|
||||||
invariant(this.description, 'Missing description');
|
invariant(this.description, 'Missing description');
|
||||||
|
|
||||||
const promises = [
|
const promises: Array<Promise<void>> = [];
|
||||||
compareAndWriteFile(
|
|
||||||
|
if (surge) {
|
||||||
|
promises.push(compareAndWriteFile(
|
||||||
this.span,
|
this.span,
|
||||||
withBannerArray(
|
withBannerArray(
|
||||||
this.title,
|
this.title,
|
||||||
@ -309,9 +325,11 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
|
|||||||
this.date,
|
this.date,
|
||||||
this.surge()
|
this.surge()
|
||||||
),
|
),
|
||||||
path.join(OUTPUT_SURGE_DIR, this.type, this.id + '.conf')
|
path.join(surgeDir, this.type, this.id + '.conf')
|
||||||
),
|
));
|
||||||
compareAndWriteFile(
|
}
|
||||||
|
if (clash) {
|
||||||
|
promises.push(compareAndWriteFile(
|
||||||
this.span,
|
this.span,
|
||||||
withBannerArray(
|
withBannerArray(
|
||||||
this.title,
|
this.title,
|
||||||
@ -319,14 +337,16 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
|
|||||||
this.date,
|
this.date,
|
||||||
this.clash()
|
this.clash()
|
||||||
),
|
),
|
||||||
path.join(OUTPUT_CLASH_DIR, this.type, this.id + '.txt')
|
path.join(clashDir, this.type, this.id + '.txt')
|
||||||
),
|
));
|
||||||
compareAndWriteFile(
|
}
|
||||||
|
if (singbox) {
|
||||||
|
promises.push(compareAndWriteFile(
|
||||||
this.span,
|
this.span,
|
||||||
this.singbox(),
|
this.singbox(),
|
||||||
path.join(OUTPUT_SINGBOX_DIR, this.type, this.id + '.json')
|
path.join(singboxDir, this.type, this.id + '.json')
|
||||||
)
|
));
|
||||||
];
|
}
|
||||||
|
|
||||||
if (this.mitmSgmodule) {
|
if (this.mitmSgmodule) {
|
||||||
const sgmodule = this.mitmSgmodule();
|
const sgmodule = this.mitmSgmodule();
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { isProbablyIpv4, isProbablyIpv6 } from 'foxts/is-probably-ip';
|
|||||||
type Preprocessed = [domain: string[], domainSuffix: string[], sortedDomainRules: string[]];
|
type Preprocessed = [domain: string[], domainSuffix: string[], sortedDomainRules: string[]];
|
||||||
|
|
||||||
export class RulesetOutput extends RuleOutput<Preprocessed> {
|
export class RulesetOutput extends RuleOutput<Preprocessed> {
|
||||||
constructor(span: Span, id: string, protected type: 'non_ip' | 'ip') {
|
constructor(span: Span, id: string, protected type: 'non_ip' | 'ip' | (string & {})) {
|
||||||
super(span, id);
|
super(span, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@ export interface DNSMapping {
|
|||||||
},
|
},
|
||||||
/** which also disallows wildcard */
|
/** which also disallows wildcard */
|
||||||
realip: boolean,
|
realip: boolean,
|
||||||
|
/** should convert to ruleset */
|
||||||
|
ruleset: boolean,
|
||||||
dns: string,
|
dns: string,
|
||||||
/**
|
/**
|
||||||
* domain[0]
|
* domain[0]
|
||||||
@ -20,6 +22,7 @@ export const DIRECTS = {
|
|||||||
dns: 'system',
|
dns: 'system',
|
||||||
hosts: {},
|
hosts: {},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: false,
|
||||||
domains: [
|
domains: [
|
||||||
'securelogin.com.cn',
|
'securelogin.com.cn',
|
||||||
'$captive.apple.com',
|
'$captive.apple.com',
|
||||||
@ -30,6 +33,7 @@ export const DIRECTS = {
|
|||||||
dns: 'system',
|
dns: 'system',
|
||||||
hosts: {},
|
hosts: {},
|
||||||
realip: true,
|
realip: true,
|
||||||
|
ruleset: false,
|
||||||
domains: [
|
domains: [
|
||||||
'+m2m',
|
'+m2m',
|
||||||
// '+ts.net', // TailScale Magic DNS
|
// '+ts.net', // TailScale Magic DNS
|
||||||
@ -47,6 +51,7 @@ export const LAN = {
|
|||||||
dns: 'system',
|
dns: 'system',
|
||||||
hosts: {},
|
hosts: {},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
'+home',
|
'+home',
|
||||||
// 'zte.home', // ZTE CPE
|
// 'zte.home', // ZTE CPE
|
||||||
@ -106,6 +111,7 @@ export const LAN = {
|
|||||||
localhost: ['127.0.0.1']
|
localhost: ['127.0.0.1']
|
||||||
},
|
},
|
||||||
realip: true,
|
realip: true,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
'+lan',
|
'+lan',
|
||||||
// 'amplifi.lan',
|
// 'amplifi.lan',
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export const DOMESTICS: Record<string, DNSMapping> = {
|
|||||||
hosts: {},
|
hosts: {},
|
||||||
dns: 'quic://dns.alidns.com:853',
|
dns: 'quic://dns.alidns.com:853',
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
'uc.cn',
|
'uc.cn',
|
||||||
// 'ucweb.com', // UC International
|
// 'ucweb.com', // UC International
|
||||||
@ -82,13 +83,18 @@ export const DOMESTICS: Record<string, DNSMapping> = {
|
|||||||
'tanx.com',
|
'tanx.com',
|
||||||
'hellobike.com',
|
'hellobike.com',
|
||||||
'+hichina.com',
|
'+hichina.com',
|
||||||
'+yunos.com'
|
'+yunos.com',
|
||||||
|
|
||||||
|
// Bilibili Aliyun CDN
|
||||||
|
'$upos-sz-mirrorali.bilivideo.com',
|
||||||
|
'$upos-sz-estgoss.bilivideo.com'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
TENCENT: {
|
TENCENT: {
|
||||||
hosts: {},
|
hosts: {},
|
||||||
dns: 'https://doh.pub/dns-query',
|
dns: 'https://doh.pub/dns-query',
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
// 'dns.pub',
|
// 'dns.pub',
|
||||||
// 'doh.pub',
|
// 'doh.pub',
|
||||||
@ -144,28 +150,11 @@ export const DOMESTICS: Record<string, DNSMapping> = {
|
|||||||
'+codehub.cn'
|
'+codehub.cn'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
BILIBILI_ALI: {
|
|
||||||
dns: 'quic://dns.alidns.com:853',
|
|
||||||
hosts: {},
|
|
||||||
realip: false,
|
|
||||||
domains: [
|
|
||||||
'$upos-sz-mirrorali.bilivideo.com',
|
|
||||||
'$upos-sz-estgoss.bilivideo.com'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
BILIBILI_BD: {
|
|
||||||
dns: '180.76.76.76',
|
|
||||||
hosts: {},
|
|
||||||
realip: false,
|
|
||||||
domains: [
|
|
||||||
'$upos-sz-mirrorbd.bilivideo.com',
|
|
||||||
'$upos-sz-mirrorbos.bilivideo.com'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
BILIBILI: {
|
BILIBILI: {
|
||||||
dns: 'https://doh.pub/dns-query',
|
dns: 'https://doh.pub/dns-query',
|
||||||
hosts: {},
|
hosts: {},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
// '$upos-sz-mirrorcoso1.bilivideo.com', // already included in bilivideo.com
|
// '$upos-sz-mirrorcoso1.bilivideo.com', // already included in bilivideo.com
|
||||||
// '$upos-sz-estgcos.bilivideo.com', // already included in bilivideo.com, tencent cloud cdn
|
// '$upos-sz-estgcos.bilivideo.com', // already included in bilivideo.com, tencent cloud cdn
|
||||||
@ -196,6 +185,7 @@ export const DOMESTICS: Record<string, DNSMapping> = {
|
|||||||
dns: 'https://doh.pub/dns-query',
|
dns: 'https://doh.pub/dns-query',
|
||||||
hosts: {},
|
hosts: {},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
'mi.com',
|
'mi.com',
|
||||||
'duokan.com',
|
'duokan.com',
|
||||||
@ -218,6 +208,7 @@ export const DOMESTICS: Record<string, DNSMapping> = {
|
|||||||
dns: '180.184.2.2',
|
dns: '180.184.2.2',
|
||||||
hosts: {},
|
hosts: {},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
'bytedance.com',
|
'bytedance.com',
|
||||||
'+bytecdn.cn',
|
'+bytecdn.cn',
|
||||||
@ -271,6 +262,7 @@ export const DOMESTICS: Record<string, DNSMapping> = {
|
|||||||
dns: '180.76.76.76',
|
dns: '180.76.76.76',
|
||||||
hosts: {},
|
hosts: {},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
'91.com',
|
'91.com',
|
||||||
'hao123.com',
|
'hao123.com',
|
||||||
@ -295,13 +287,18 @@ export const DOMESTICS: Record<string, DNSMapping> = {
|
|||||||
'+bdydns.com',
|
'+bdydns.com',
|
||||||
'+jomoxc.com', // Baidu PCDN, of sort
|
'+jomoxc.com', // Baidu PCDN, of sort
|
||||||
'+duapp.com',
|
'+duapp.com',
|
||||||
'+antpcdn.com' // Baidu PCDN
|
'+antpcdn.com', // Baidu PCDN
|
||||||
|
|
||||||
|
// Bilibili Baidu CDN
|
||||||
|
'$upos-sz-mirrorbd.bilivideo.com',
|
||||||
|
'$upos-sz-mirrorbos.bilivideo.com'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
QIHOO360: {
|
QIHOO360: {
|
||||||
hosts: {},
|
hosts: {},
|
||||||
dns: 'https://doh.360.cn/dns-query',
|
dns: 'https://doh.360.cn/dns-query',
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: true,
|
||||||
domains: [
|
domains: [
|
||||||
'+qhimg.com',
|
'+qhimg.com',
|
||||||
'+qhimgs.com',
|
'+qhimgs.com',
|
||||||
@ -350,6 +347,7 @@ export const DOH_BOOTSTRAP: Record<string, DNSMapping> = {
|
|||||||
'dns.alidns.com': ['223.5.5.5', '223.6.6.6', '2400:3200:baba::1', '2400:3200::1']
|
'dns.alidns.com': ['223.5.5.5', '223.6.6.6', '2400:3200:baba::1', '2400:3200::1']
|
||||||
},
|
},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: false,
|
||||||
dns: 'quic://223.5.5.5:853',
|
dns: 'quic://223.5.5.5:853',
|
||||||
domains: [
|
domains: [
|
||||||
'$dns.alidns.com'
|
'$dns.alidns.com'
|
||||||
@ -362,6 +360,7 @@ export const DOH_BOOTSTRAP: Record<string, DNSMapping> = {
|
|||||||
// 'dns.pub': ['120.53.53.53', '1.12.12.12']
|
// 'dns.pub': ['120.53.53.53', '1.12.12.12']
|
||||||
},
|
},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: false,
|
||||||
dns: 'https://1.12.12.12/dns-query',
|
dns: 'https://1.12.12.12/dns-query',
|
||||||
domains: [
|
domains: [
|
||||||
// '$dot.pub',
|
// '$dot.pub',
|
||||||
@ -382,6 +381,7 @@ export const DOH_BOOTSTRAP: Record<string, DNSMapping> = {
|
|||||||
// dot.360.net CNAME dns.360.net
|
// dot.360.net CNAME dns.360.net
|
||||||
},
|
},
|
||||||
realip: false,
|
realip: false,
|
||||||
|
ruleset: false,
|
||||||
// Surge only supports UDP 53 or Hosts as the bootstrap server of domain DoH
|
// Surge only supports UDP 53 or Hosts as the bootstrap server of domain DoH
|
||||||
dns: '101.198.198.198', // 'https://101.198.198.198/dns-query', // https://101.198.199.200/dns-query
|
dns: '101.198.198.198', // 'https://101.198.198.198/dns-query', // https://101.198.199.200/dns-query
|
||||||
domains: [
|
domains: [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user