AnyTLS 协议
AnyTLS 是一种基于 TLS 的代理协议,具有会话多路复用、可配置的 padding 方案和空闲会话管理等特性。sing-box 集成了来自 anytls 项目的外部 sing-anytls 库。
源码: protocol/anytls/inbound.go, protocol/anytls/outbound.go, sing-anytls
架构概览
go
// Inbound
type Inbound struct {
inbound.Adapter
tlsConfig tls.ServerConfig
router adapter.ConnectionRouterEx
logger logger.ContextLogger
listener *listener.Listener
service *anytls.Service
}
// Outbound
type Outbound struct {
outbound.Adapter
dialer tls.Dialer
server M.Socksaddr
tlsConfig tls.Config
client *anytls.Client
uotClient *uot.Client
logger log.ContextLogger
}Inbound 实现
TLS 处理
与 Hysteria2 要求 TLS 不同,AnyTLS 在 inbound 上使 TLS 可选——TLS handshake 在传递给服务之前显式处理:
go
if options.TLS != nil && options.TLS.Enabled {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
inbound.tlsConfig = tlsConfig
}当配置了 TLS 时,每个连接在协议处理前都会进行 TLS handshake:
go
func (h *Inbound) NewConnectionEx(ctx, conn, metadata, onClose) {
if h.tlsConfig != nil {
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
return
}
conn = tlsConn
}
err := h.service.NewConnection(ctx, conn, metadata.Source, onClose)
}Padding 方案
AnyTLS 使用可配置的 padding 方案来混淆流量模式。方案定义为多行字符串:
go
paddingScheme := padding.DefaultPaddingScheme
if len(options.PaddingScheme) > 0 {
paddingScheme = []byte(strings.Join(options.PaddingScheme, "\n"))
}
service, _ := anytls.NewService(anytls.ServiceConfig{
Users: common.Map(options.Users, func(it option.AnyTLSUser) anytls.User {
return (anytls.User)(it)
}),
PaddingScheme: paddingScheme,
Handler: (*inboundHandler)(inbound),
Logger: logger,
})仅 TCP 监听器
AnyTLS 仅支持 TCP 连接:
go
inbound.listener = listener.New(listener.Options{
Network: []string{N.NetworkTCP},
ConnectionHandler: inbound,
})Inbound 处理器模式
AnyTLS 使用类型转换处理器模式(与 ShadowTLS 相同)。Inbound 类型处理原始连接,而 inboundHandler 类型别名处理解码后的连接:
go
type inboundHandler Inbound
func (h *inboundHandler) NewConnectionEx(ctx, conn, source, destination, onClose) {
metadata.Destination = destination.Unwrap()
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
metadata.User = userName
}
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}Outbound 实现
TLS 要求
outbound 要求 TLS:
go
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}TCP Fast Open 不兼容性
AnyTLS 明确与 TCP Fast Open 不兼容。TFO 创建延迟连接(推迟到首次写入时建立),但 AnyTLS 在 handshake 期间需要远程地址:
go
if options.DialerOptions.TCPFastOpen {
return nil, E.New("tcp_fast_open is not supported with anytls outbound")
}会话池化
客户端维护一个空闲 TLS 会话池以复用连接。会话管理可配置:
go
client, _ := anytls.NewClient(ctx, anytls.ClientConfig{
Password: options.Password,
IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(),
IdleSessionTimeout: options.IdleSessionTimeout.Build(),
MinIdleSession: options.MinIdleSession,
DialOut: outbound.dialOut,
Logger: logger,
})关键会话参数:
- IdleSessionCheckInterval:检查空闲会话的频率
- IdleSessionTimeout:空闲会话被关闭前的等待时长
- MinIdleSession:池中维护的最少空闲会话数
DialOut 函数
DialOut 回调为会话池创建新的 TLS 连接:
go
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
return h.dialer.DialTLSContext(ctx, h.server)
}通过 CreateProxy 建立 TCP 连接
go
func (h *Outbound) DialContext(ctx, network, destination) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP:
return h.client.CreateProxy(ctx, destination)
case N.NetworkUDP:
return h.uotClient.DialContext(ctx, network, destination)
}
}通过 UoT 支持 UDP
UDP 通过 uot 包的 UDP-over-TCP 支持。UoT 客户端包装 AnyTLS 客户端的 CreateProxy 方法:
go
outbound.uotClient = &uot.Client{
Dialer: (anytlsDialer)(client.CreateProxy),
Version: uot.Version,
}anytlsDialer 适配器将 CreateProxy 函数签名转换为 N.Dialer interface:
go
type anytlsDialer func(ctx context.Context, destination M.Socksaddr) (net.Conn, error)
func (d anytlsDialer) DialContext(ctx, network, destination) (net.Conn, error) {
return d(ctx, destination)
}
func (d anytlsDialer) ListenPacket(ctx, destination) (net.PacketConn, error) {
return nil, os.ErrInvalid
}UoT Router(Inbound)
inbound 使用 UoT 支持包装其路由器:
go
inbound.router = uot.NewRouter(router, logger)配置示例
Inbound
json
{
"type": "anytls",
"tag": "anytls-in",
"listen": "::",
"listen_port": 443,
"users": [
{ "name": "user1", "password": "user-password" }
],
"padding_scheme": [
"0:100",
"200:500"
],
"tls": {
"enabled": true,
"certificate_path": "/path/to/cert.pem",
"key_path": "/path/to/key.pem"
}
}Outbound
json
{
"type": "anytls",
"tag": "anytls-out",
"server": "example.com",
"server_port": 443,
"password": "user-password",
"idle_session_check_interval": "30s",
"idle_session_timeout": "30s",
"min_idle_session": 1,
"tls": {
"enabled": true,
"server_name": "example.com"
}
}