Skip to content

Commit 9e611ac

Browse files
authored
Merge pull request #324 from ZedRover/master
2 parents cb98bcd + 6ea23ff commit 9e611ac

File tree

17 files changed

+241
-17
lines changed

17 files changed

+241
-17
lines changed

docs/guide/custom-provider.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ module.exports = defineCustomProvider(async function () {
4949

5050
| 类型 | 描述 | 备注 |
5151
| :----------------------------------------------: | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
52-
| `custom` <Badge text="推荐" vertical="middle" /> | 自己维护的节点 | 支持 Shadowsocks, Shadowsocksr, Snell, HTTPS, HTTP, Vmess, Vless, Hysteria 2, Socks5, Tuic, Trojan, Wireguard |
53-
| `clash` <Badge text="推荐" vertical="middle" /> | Clash 配置 | 支持 Shadowsocks, Shadowsocksr, Snell, HTTPS, HTTP, Vmess, Vless, Hysteria 2, Socks5, Tuic, Trojan, Wireguard |
52+
| `custom` <Badge text="推荐" vertical="middle" /> | 自己维护的节点 | 支持 Shadowsocks, Shadowsocksr, Snell, HTTPS, HTTP, Vmess, Vless, Hysteria 2, AnyTLS, Socks5, Tuic, Trojan, Wireguard |
53+
| `clash` <Badge text="推荐" vertical="middle" /> | Clash 配置 | 支持 Shadowsocks, Shadowsocksr, Snell, HTTPS, HTTP, Vmess, Vless, Hysteria 2, AnyTLS, Socks5, Tuic, Trojan, Wireguard |
5454
| `trojan` | Trojan 订阅 | Shadowrocket 支持的 Trojan 订阅格式 |
5555
| `shadowsocks_json_subscribe` | 针对 Windows 客户端的 Shadowsocks 订阅地址 | 通常命名为 _gui-config.json_ |
5656
| `shadowsocks_subscribe` | 通用的 Shadowsocks 订阅地址 | |
@@ -547,6 +547,27 @@ Clash 需要在配置中开启 `clashConfig.enableHysteria2`。
547547
}
548548
```
549549

550+
### AnyTLS
551+
552+
当前支持为 Clash、Surge 和 sing-box 生成 AnyTLS 节点。
553+
554+
```json5
555+
{
556+
type: 'anytls',
557+
nodeName: 'AnyTLS',
558+
hostname: 'anytls.example.com',
559+
port: 443,
560+
password: 'password',
561+
udpRelay: false, // 可选
562+
sni: 'sni.example.com', // 可选
563+
alpn: ['h2', 'http/1.1'], // 可选
564+
skipCertVerify: false, // 可选
565+
idleSessionCheckInterval: 0, // 可选
566+
idleSessionTimeout: 0, // 可选
567+
minIdleSessions: 0, // 可选
568+
}
569+
```
570+
550571
## SSD 订阅
551572

552573
```js
@@ -798,7 +819,7 @@ module.exports = {
798819

799820
:::warning 注意
800821
1. TLS 1.3 需要服务端支持;
801-
2. 支持 TLS 的节点类型有 Shadowsocks with v2ray-plugin(tls), Vmess(tls), HTTPS;
822+
2. 支持 TLS 的节点类型有 Shadowsocks with v2ray-plugin(tls), Vmess(tls), HTTPS, AnyTLS
802823
:::
803824

804825
### nodeConfig.skipCertVerify
@@ -809,7 +830,7 @@ module.exports = {
809830
关闭 TLS 节点的证书检查。
810831

811832
:::warning 注意
812-
1. 支持 TLS 的节点类型有 Shadowsocks with v2ray-plugin(tls), Vmess(tls), HTTPS;
833+
1. 支持 TLS 的节点类型有 Shadowsocks with v2ray-plugin(tls), Vmess(tls), HTTPS, AnyTLS
813834
2. 请不要随意将证书检查关闭;
814835
:::
815836

src/__tests__/__snapshots__/index.test.ts.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Generated by [AVA](https://avajs.dev).
99
> Snapshot 1
1010
1111
{
12+
anytlsFilter: Function anytlsFilter {},
1213
applyFilter: Function applyFilter {},
1314
chinaBackFilter: Function chinaBackFilter {},
1415
chinaOutFilter: Function {},
26 Bytes
Binary file not shown.

src/filters/filters.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,6 @@ export const hysteria2Filter: NodeFilterType = (item) =>
121121
// istanbul ignore next
122122
export const vlessFilter: NodeFilterType = (item) =>
123123
item.type === NodeTypeEnum.Vless
124+
// istanbul ignore next
125+
export const anytlsFilter: NodeFilterType = (item) =>
126+
item.type === NodeTypeEnum.AnyTLS

src/provider/ClashProvider.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
STASH_SUPPORTED_VMESS_NETWORK,
1010
} from '../constant'
1111
import {
12+
AnyTLSNodeConfig,
13+
AnyTLSNodeConfigInput,
1214
ClashProviderConfig,
1315
HttpNodeConfig,
1416
HttpsNodeConfig,
@@ -34,6 +36,7 @@ import {
3436
} from '../utils'
3537
import relayableUrl from '../utils/relayable-url'
3638
import {
39+
AnyTLSNodeConfigValidator,
3740
Hysteria2NodeConfigValidator,
3841
TuicNodeConfigValidator,
3942
} from '../validators'
@@ -59,6 +62,7 @@ type SupportConfigTypes =
5962
| TuicNodeConfig
6063
| Hysteria2NodeConfig
6164
| Socks5NodeConfig
65+
| AnyTLSNodeConfig
6266

6367
const logger = createLogger({
6468
service: 'surgio:ClashProvider',
@@ -665,6 +669,45 @@ export const parseClashConfig = (
665669
return socks5Node
666670
}
667671

672+
case 'anytls': {
673+
const input: AnyTLSNodeConfigInput = {
674+
type: NodeTypeEnum.AnyTLS,
675+
nodeName: item.name,
676+
hostname: item.server,
677+
port: item.port,
678+
password: item.password,
679+
...('skip-cert-verify' in item
680+
? { skipCertVerify: item['skip-cert-verify'] === true }
681+
: null),
682+
...('alpn' in item ? { alpn: item.alpn } : null),
683+
...('sni' in item ? { sni: item.sni } : null),
684+
udpRelay: resolveUdpRelay(item.udp, udpRelay),
685+
tls13: tls13 ?? false,
686+
...('idle-session-check-interval' in item
687+
? {
688+
idleSessionCheckInterval: item['idle-session-check-interval'],
689+
}
690+
: null),
691+
...('idle-session-timeout' in item
692+
? { idleSessionTimeout: item['idle-session-timeout'] }
693+
: null),
694+
...('min-idle-session' in item
695+
? { minIdleSessions: item['min-idle-session'] }
696+
: null),
697+
}
698+
699+
const result = AnyTLSNodeConfigValidator.safeParse(input)
700+
701+
// istanbul ignore next
702+
if (!result.success) {
703+
throw new SurgioError('AnyTLS 节点配置校验失败', {
704+
cause: result.error,
705+
})
706+
}
707+
708+
return result.data
709+
}
710+
668711
default:
669712
logger.warn(
670713
`不支持从 Clash 订阅中读取 ${item.type} 的节点,节点 ${item.name} 会被省略`,

src/provider/CustomProvider.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
TuicNodeConfigValidator,
2121
Hysteria2NodeConfigValidator,
2222
VlessNodeConfigValidator,
23+
AnyTLSNodeConfigValidator,
2324
} from '../validators'
2425

2526
import Provider from './Provider'
@@ -130,6 +131,9 @@ export default class CustomProvider extends Provider {
130131
case NodeTypeEnum.Vless:
131132
return VlessNodeConfigValidator.parse(node)
132133

134+
case NodeTypeEnum.AnyTLS:
135+
return AnyTLSNodeConfigValidator.parse(node)
136+
133137
default:
134138
throw new TypeError(`无法识别的节点类型:${type}`)
135139
}

src/provider/__tests__/ClashProvider.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,3 +1146,37 @@ test('parseClashConfig hysteria2 invalid obfs', (t) => {
11461146
},
11471147
)
11481148
})
1149+
1150+
test('parseClashConfig anytls options', (t) => {
1151+
t.deepEqual(
1152+
parseClashConfig([
1153+
{
1154+
type: 'anytls',
1155+
name: 'anytls',
1156+
server: 'server',
1157+
port: 443,
1158+
password: 'password',
1159+
udp: false,
1160+
'skip-cert-verify': false,
1161+
'idle-session-check-interval': 0,
1162+
'idle-session-timeout': 0,
1163+
'min-idle-session': 0,
1164+
},
1165+
]),
1166+
[
1167+
{
1168+
type: NodeTypeEnum.AnyTLS,
1169+
nodeName: 'anytls',
1170+
hostname: 'server',
1171+
port: 443,
1172+
password: 'password',
1173+
udpRelay: false,
1174+
tls13: false,
1175+
skipCertVerify: false,
1176+
idleSessionCheckInterval: 0,
1177+
idleSessionTimeout: 0,
1178+
minIdleSessions: 0,
1179+
},
1180+
],
1181+
)
1182+
})

src/provider/__tests__/Provider.test.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ import Provider from '../Provider'
99
const sandbox = sinon.createSandbox()
1010

1111
class TestProvider extends Provider {
12-
async getNodeList() {
12+
constructor(name: string, config: any) {
13+
super(name, config)
14+
}
15+
16+
getNodeList = async () => {
1317
return []
1418
}
1519

16-
async getNodeListV2() {
20+
getNodeListV2 = async () => {
1721
return {
1822
nodeList: [],
1923
}
@@ -48,8 +52,7 @@ test('Provider determineRequestHeaders filters headers by allowlist', (t) => {
4852
nodeList: [],
4953
})
5054

51-
// @ts-expect-error - test-only override of private property
52-
provider.passGatewayRequestHeaders = ['accept-language']
55+
;(provider as any).passGatewayRequestHeaders = ['accept-language']
5356

5457
const headers = provider.determineRequestHeaders(undefined, {
5558
'accept-language': 'en-US',
@@ -69,8 +72,7 @@ test('Provider determineRequestHeaders uses requestUserAgent when allowed', (t)
6972
requestUserAgent: 'config-ua',
7073
})
7174

72-
// @ts-expect-error - test-only override of private property
73-
provider.passGatewayRequestHeaders = ['user-agent']
75+
;(provider as any).passGatewayRequestHeaders = ['user-agent']
7476

7577
const headers = provider.determineRequestHeaders('param-ua', {
7678
'user-agent': 'header-ua',
@@ -86,8 +88,7 @@ test('Provider determineRequestHeaders falls back to header user-agent', (t) =>
8688
requestUserAgent: 'config-ua',
8789
})
8890

89-
// @ts-expect-error - test-only override of private property
90-
provider.passGatewayRequestHeaders = ['user-agent']
91+
;(provider as any).passGatewayRequestHeaders = ['user-agent']
9192

9293
const headers = provider.determineRequestHeaders(undefined, {
9394
'user-agent': 'header-ua',
@@ -103,8 +104,7 @@ test('Provider determineRequestHeaders falls back to config user-agent', (t) =>
103104
requestUserAgent: 'config-ua',
104105
})
105106

106-
// @ts-expect-error - test-only override of private property
107-
provider.passGatewayRequestHeaders = ['user-agent']
107+
;(provider as any).passGatewayRequestHeaders = ['user-agent']
108108

109109
const headers = provider.determineRequestHeaders()
110110

@@ -117,8 +117,7 @@ test('Provider determineRequestHeaders normalizes header casing', (t) => {
117117
nodeList: [],
118118
})
119119

120-
// @ts-expect-error - test-only override of private property
121-
provider.passGatewayRequestHeaders = ['accept-language']
120+
;(provider as any).passGatewayRequestHeaders = ['accept-language']
122121

123122
const headers = provider.determineRequestHeaders(undefined, {
124123
'Accept-Language': 'en-GB',

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
Hysteria2NodeConfigValidator,
2121
ClashCoreValidator,
2222
VlessNodeConfigValidator,
23+
AnyTLSNodeConfigValidator,
2324
} from './validators'
2425

2526
import type { Provider, GetNodeListParams } from './provider'
@@ -37,6 +38,7 @@ export enum NodeTypeEnum {
3738
Tuic = 'tuic',
3839
Wireguard = 'wireguard',
3940
Hysteria2 = 'hysteria2',
41+
AnyTLS = 'anytls',
4042
}
4143

4244
export enum SupportProviderEnum {
@@ -213,6 +215,11 @@ export type Hysteria2NodeConfigInput = z.input<
213215
export type Hysteria2NodeConfig = z.infer<typeof Hysteria2NodeConfigValidator> &
214216
SurgioInternals
215217

218+
export type AnyTLSNodeConfigInput = z.input<typeof AnyTLSNodeConfigValidator>
219+
220+
export type AnyTLSNodeConfig = z.infer<typeof AnyTLSNodeConfigValidator> &
221+
SurgioInternals
222+
216223
export interface SurgioInternals {
217224
provider?: Provider
218225
}
@@ -246,6 +253,7 @@ export type PossibleNodeConfigType =
246253
| TuicNodeConfig
247254
| WireguardNodeConfig
248255
| Hysteria2NodeConfig
256+
| AnyTLSNodeConfig
249257

250258
export type PossibleProviderConfigType =
251259
| BlackSSLProviderConfig

src/utils/__tests__/clash.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,37 @@ test('getClashNodes', async (t) => {
706706
[],
707707
)
708708

709+
t.deepEqual(
710+
clash.getClashNodes([
711+
{
712+
type: NodeTypeEnum.AnyTLS,
713+
nodeName: 'anytls',
714+
hostname: 'example.com',
715+
port: 443,
716+
password: 'password',
717+
udpRelay: false,
718+
skipCertVerify: false,
719+
idleSessionCheckInterval: 0,
720+
idleSessionTimeout: 0,
721+
minIdleSessions: 0,
722+
},
723+
]),
724+
[
725+
{
726+
type: 'anytls',
727+
name: 'anytls',
728+
server: 'example.com',
729+
port: 443,
730+
password: 'password',
731+
udp: false,
732+
'skip-cert-verify': false,
733+
'idle-session-check-interval': 0,
734+
'idle-session-timeout': 0,
735+
'min-idle-session': 0,
736+
},
737+
],
738+
)
739+
709740
t.deepEqual(
710741
clash.getClashNodes([
711742
{

0 commit comments

Comments
 (0)