Skip to content

Commit

Permalink
feat: 支持在输出 external 时解析域名
Browse files Browse the repository at this point in the history
  • Loading branch information
geekdada committed Nov 18, 2019
1 parent d99f9fc commit 1f78f44
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 79 deletions.
9 changes: 9 additions & 0 deletions docs/guide/custom-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,15 @@ SSR 的可执行文件地址。请使用 libev 版本的二进制文件,可以

定义生成 Vmess 节点配置的类型,默认使用 External Provider 的形式,兼容性更好。也可以选择使用 `native` 的方式。

#### surgeConfig.resolveHostname <Badge text="v1.5.0" vertical="middle" />

- 类型: `boolean`
- 默认值: `false`

在 Surge 官方对 External Provider 的 [解释](https://medium.com/@Blankwonder/surge-mac-new-features-external-proxy-provider-375e0e9ea660) 中提到,为了不让代理进程(如 ssr-local)的流量经过 Surge 的 TUN 模块,需要额外指定 `addresses` 参数。在之前版本的 Surgio 里,生成的配置不会对域名进行解析,导致实际使用中仍然会造成额外的性能损耗。

打开这个选项后,Surgio 会在生成配置的时候解析域名。不过,这必然会造成生成时间延长,所以请按照个人的需要进行选择。

### gateway <Badge text="v1.1.0" vertical="middle" />

- 类型: `object`
Expand Down
44 changes: 32 additions & 12 deletions lib/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import getEngine from './template';
import {
ArtifactConfig,
CommandConfig,
NodeNameFilterType,
NodeNameFilterType, NodeTypeEnum,
PossibleNodeConfigType,
ProviderConfig,
RemoteSnippet,
SimpleNodeConfig,
} from './types';
import {
getClashNodeNames,
getClashNodes,
getDownloadUrl,
getNodeNames,
Expand All @@ -35,6 +34,7 @@ import {
toBase64,
toUrlSafeBase64,
} from './utils';
import { isIp, resolveDomain } from './utils/dns';
import {
hkFilter, japanFilter, koreaFilter,
netflixFilter as defaultNetflixFilter, singaporeFilter, taiwanFilter,
Expand Down Expand Up @@ -151,27 +151,47 @@ export async function generate(
}
}

nodeConfigList.forEach(nodeConfig => {
nodeConfigList = await Bluebird.map(nodeConfigList, async nodeConfig => {
let isValid = false;

if (nodeConfig.enable === false) {
return null;
}

if (!provider.nodeFilter) {
isValid = true;
} else if (provider.nodeFilter(nodeConfig)) {
isValid = true;
}

if (config.binPath && config.binPath[nodeConfig.type]) {
nodeConfig.binPath = config.binPath[nodeConfig.type];
nodeConfig.localPort = provider.nextPort;
}
if (isValid) {
if (config.binPath && config.binPath[nodeConfig.type]) {
nodeConfig.binPath = config.binPath[nodeConfig.type];
nodeConfig.localPort = provider.nextPort;
}

nodeConfig.surgeConfig = config.surgeConfig;

nodeConfig.surgeConfig = config.surgeConfig;
if (provider.addFlag) {
nodeConfig.nodeName = prependFlag(nodeConfig.nodeName);
}

if (provider.addFlag) {
nodeConfig.nodeName = prependFlag(nodeConfig.nodeName);
if (
config.surgeConfig.resolveHostname &&
!isIp(nodeConfig.hostname) &&
[NodeTypeEnum.Vmess, NodeTypeEnum.Shadowsocksr].includes(nodeConfig.type)
) {
nodeConfig.hostnameIp = await resolveDomain(nodeConfig.hostname);
}

return nodeConfig;
}

if (isValid) {
return null;
});

nodeConfigList.forEach(nodeConfig => {
if (nodeConfig) {
nodeNameList.push({
type: nodeConfig.type,
enable: nodeConfig.enable,
Expand All @@ -182,7 +202,7 @@ export async function generate(
});

spinner.text = `已处理 Provider ${++progress}/${providerList.length}...`;
}
};

await Bluebird.map(providerList, providerMapper, { concurrency: NETWORK_CONCURRENCY });

Expand Down
4 changes: 3 additions & 1 deletion lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export interface CommandConfig {
vmess?: string; // tslint:disable-line
};
readonly surgeConfig?: {
readonly v2ray: 'native'|'external';
readonly v2ray?: 'native'|'external';
readonly resolveHostname?: boolean;
};
readonly gateway?: {
readonly accessToken?: string;
Expand Down Expand Up @@ -180,6 +181,7 @@ export interface SimpleNodeConfig {
binPath?: string; // tslint:disable-line
localPort?: number; // tslint:disable-line
surgeConfig?: CommandConfig['surgeConfig']; // tslint:disable-line
hostnameIp?: ReadonlyArray<string>; // tslint:disable-line
}

export interface PlainObject { readonly [name: string]: any }
Expand Down
4 changes: 3 additions & 1 deletion lib/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const normalizeConfig = (cwd: string, userConfig: Partial<CommandConfig>)
configDir: ensureConfigFolder(),
surgeConfig: {
v2ray: 'external',
resolveHostname: false,
},
proxyTestUrl: PROXY_TEST_URL,
proxyTestInterval: PROXY_TEST_INTERVAL,
Expand Down Expand Up @@ -91,7 +92,8 @@ export const validateConfig = (userConfig: Partial<CommandConfig>): void => {
vmess: Joi.string().pattern(/^\//),
}),
surgeConfig: Joi.object({
v2ray: Joi.string().valid('native', 'external')
v2ray: Joi.string().valid('native', 'external'),
resolveHostname: Joi.boolean(),
}),
analytics: Joi.boolean(),
gateway: Joi.object({
Expand Down
30 changes: 30 additions & 0 deletions lib/utils/dns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { promises as dns } from 'dns';
import Debug from 'debug';
import LRU from 'lru-cache';

const debug = Debug('surgio:utils:dns');
const Resolver = dns.Resolver;
const resolver = new Resolver();
const DomainCache = new LRU<string, ReadonlyArray<string>>({
max: 1000,
});

resolver.setServers(['223.5.5.5', '114.114.114.114', '8.8.8.8', '1.1.1.1']);

export const resolveDomain = async (domain: string): Promise<ReadonlyArray<string>> => {
if (DomainCache.has(domain)) {
return DomainCache.get(domain);
}

debug(`try to resolve domain ${domain}`);
const records = await resolver.resolve4(domain, { ttl: true });
debug(`resolved domain ${domain}: ${JSON.stringify(records)}`);

const address = records.map(item => item.address);

DomainCache.set(domain, address, records[0].ttl * 1000);

return address;
};

export const isIp = (str: string): boolean => /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/gm.test(str);
26 changes: 18 additions & 8 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,14 +383,19 @@ export const getSurgeNodes = (
const configString = [
'external',
`exec = ${JSON.stringify(config.binPath)}`,
...(args).map(arg => `args = ${JSON.stringify(arg)}`),
...(args.map(arg => `args = ${JSON.stringify(arg)}`)),
`local-port = ${config.localPort}`,
`addresses = ${config.hostname}`,
].join(', ');
];

if (config.hostnameIp) {
configString.push(...config.hostnameIp.map(item => `addresses = ${item}`));
} else {
configString.push(`addresses = ${config.hostname}`);
}

return ([
config.nodeName,
configString,
configString.join(', '),
].join(' = '));
}

Expand Down Expand Up @@ -451,18 +456,23 @@ export const getSurgeNodes = (
const configString = [
'external',
`exec = ${JSON.stringify(config.binPath)}`,
...(args).map(arg => `args = ${JSON.stringify(arg)}`),
...(args.map(arg => `args = ${JSON.stringify(arg)}`)),
`local-port = ${config.localPort}`,
`addresses = ${config.hostname}`,
].join(', ');
];

if (config.hostnameIp) {
configString.push(...config.hostnameIp.map(item => `addresses = ${item}`));
} else {
configString.push(`addresses = ${config.hostname}`);
}

if (process.env.NODE_ENV !== 'test') {
fs.writeJSONSync(jsonFilePath, jsonFile);
}

return ([
config.nodeName,
configString,
configString.join(', '),
].join(' = '));
}
}
Expand Down
114 changes: 57 additions & 57 deletions test/snapshots/cli.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,6 @@ The actual snapshot is saved in `cli.test.ts.snap`.

Generated by [AVA](https://ava.li).

## custom filter

> Snapshot 1
`🇺🇸US 1 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, obfs=tls, obfs-host=gateway-carry.icloud.com␊
🇺🇸US 2 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module␊
----␊
🇺🇸US 1, 🇺🇸US 2␊
----␊
----␊
🇺🇸US 1␊
----␊
Proxy:␊
- type: ss␊
cipher: chacha20-ietf-poly1305␊
name: 🇺🇸US 1␊
password: password␊
port: "443"␊
server: us.example.com␊
udp: false␊
plugin: obfs␊
plugin-opts:␊
mode: tls␊
host: gateway-carry.icloud.com␊
- type: ss␊
cipher: chacha20-ietf-poly1305␊
name: 🇺🇸US 2␊
password: password␊
port: "443"␊
server: us.example.com␊
udp: false␊
Proxy Group:␊
- type: select␊
name: global filter␊
proxies:␊
- 🇺🇸US 1␊
- type: select␊
name: provider filter␊
proxies:␊
- 🇺🇸US 1␊
- 🇺🇸US 2␊
- type: select␊
name: sort filter␊
proxies:␊
- 🇺🇸US 2␊
- 🇺🇸US 1␊
----␊
🇺🇸US 2 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module␊
🇺🇸US 1 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, obfs=tls, obfs-host=gateway-carry.icloud.com␊
----␊
🇺🇸US 2, 🇺🇸US 1␊
`

## cli works

> Snapshot 1
Expand All @@ -77,7 +22,7 @@ Generated by [AVA](https://ava.li).
ss1 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=true␊
ss2 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=false, obfs=tls, obfs-host=www.bing.com␊
ss4 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=false, obfs=tls, obfs-host=example.com␊
测试中文 = external, exec = "/usr/local/bin/ssr-local", args = "-s", args = "127.0.0.1", args = "-p", args = "1234", args = "-m", args = "aes-128-cfb", args = "-o", args = "tls1.2_ticket_auth", args = "-O", args = "auth_aes128_md5", args = "-k", args = "aaabbb", args = "-l", args = "61107", args = "-b", args = "127.0.0.1", args = "-g", args = "breakwa11.moe", local-port = 61107, addresses = 127.0.0.1␊
测试中文 = external, exec = "/usr/local/bin/ssr-local", args = "-s", args = "127.0.0.1", args = "-p", args = "1234", args = "-m", args = "aes-128-cfb", args = "-o", args = "tls1.2_ticket_auth", args = "-O", args = "auth_aes128_md5", args = "-k", args = "aaabbb", args = "-l", args = "61105", args = "-b", args = "127.0.0.1", args = "-g", args = "breakwa11.moe", local-port = 61105, addresses = 127.0.0.1␊
----␊
🇺🇸US 1, 🇺🇸US 2, 🇺🇲 US, Snell, HTTPS, 🇺🇸US 1, 🇺🇸US 2, 🇺🇸US 3, 🇺🇸US 4, 测试 1, 测试 2, ss1, ss2, ss4, 测试中文␊
----␊
Expand All @@ -96,7 +41,7 @@ Generated by [AVA](https://ava.li).
shadowsocks=server:443, method=chacha20-ietf-poly1305, password=password, obfs=tls, obfs-host=example.com, tag=ss4␊
shadowsocks=127.0.0.1:1234, method=aes-128-cfb, password=aaabbb, ssr-protocol=auth_aes128_md5, ssr-protocol-param=, obfs=tls1.2_ticket_auth, obfs-host=breakwa11.moe, udp-relay=true, fast-open=true, tag=测试中文␊
----␊
测试中文 = external, exec = "/usr/local/bin/ssr-local", args = "-s", args = "127.0.0.1", args = "-p", args = "1234", args = "-m", args = "aes-128-cfb", args = "-o", args = "tls1.2_ticket_auth", args = "-O", args = "auth_aes128_md5", args = "-k", args = "aaabbb", args = "-l", args = "61107", args = "-b", args = "127.0.0.1", args = "-g", args = "breakwa11.moe", local-port = 61107, addresses = 127.0.0.1␊
测试中文 = external, exec = "/usr/local/bin/ssr-local", args = "-s", args = "127.0.0.1", args = "-p", args = "1234", args = "-m", args = "aes-128-cfb", args = "-o", args = "tls1.2_ticket_auth", args = "-O", args = "auth_aes128_md5", args = "-k", args = "aaabbb", args = "-l", args = "61105", args = "-b", args = "127.0.0.1", args = "-g", args = "breakwa11.moe", local-port = 61105, addresses = 127.0.0.1␊
----␊
测试 1, vmess1, vmess1://1386f85e-657b-4d6e-9d56-78badb75e1fd@1.1.1.1:8080/?network=ws&tls=false␊
测试 2, vmess1, vmess1://1386f85e-657b-4d6e-9d56-78badb75e1fd@1.1.1.1:8080/?network=tcp&tls=false␊
Expand Down Expand Up @@ -278,3 +223,58 @@ Generated by [AVA](https://ava.li).
- 测试中文␊
`

## custom filter

> Snapshot 1
`🇺🇸US 1 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, obfs=tls, obfs-host=gateway-carry.icloud.com␊
🇺🇸US 2 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module␊
----␊
🇺🇸US 1, 🇺🇸US 2␊
----␊
----␊
🇺🇸US 1␊
----␊
Proxy:␊
- type: ss␊
cipher: chacha20-ietf-poly1305␊
name: 🇺🇸US 1␊
password: password␊
port: "443"␊
server: us.example.com␊
udp: false␊
plugin: obfs␊
plugin-opts:␊
mode: tls␊
host: gateway-carry.icloud.com␊
- type: ss␊
cipher: chacha20-ietf-poly1305␊
name: 🇺🇸US 2␊
password: password␊
port: "443"␊
server: us.example.com␊
udp: false␊
Proxy Group:␊
- type: select␊
name: global filter␊
proxies:␊
- 🇺🇸US 1␊
- type: select␊
name: provider filter␊
proxies:␊
- 🇺🇸US 1␊
- 🇺🇸US 2␊
- type: select␊
name: sort filter␊
proxies:␊
- 🇺🇸US 2␊
- 🇺🇸US 1␊
----␊
🇺🇸US 2 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module␊
🇺🇸US 1 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, obfs=tls, obfs-host=gateway-carry.icloud.com␊
----␊
🇺🇸US 2, 🇺🇸US 1␊
`
Binary file modified test/snapshots/cli.test.ts.snap
Binary file not shown.

0 comments on commit 1f78f44

Please sign in to comment.