AnyTLS Protocol
AnyTLS is a TLS-based proxy protocol featuring session multiplexing, configurable padding schemes, and idle session management. sing-box integrates the external sing-anytls library from the anytls project.
Source: protocol/anytls/inbound.go, protocol/anytls/outbound.go, sing-anytls
Architecture Overview
// 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 Implementation
TLS Handling
Unlike protocols like Hysteria2 which require TLS, AnyTLS makes TLS optional on the inbound -- the TLS handshake is handled explicitly before passing to the service:
if options.TLS != nil && options.TLS.Enabled {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
inbound.tlsConfig = tlsConfig
}When TLS is configured, each connection undergoes a TLS handshake before protocol processing:
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 Scheme
AnyTLS uses a configurable padding scheme to obfuscate traffic patterns. The scheme is defined as a multi-line string:
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-Only Listener
AnyTLS only supports TCP connections:
inbound.listener = listener.New(listener.Options{
Network: []string{N.NetworkTCP},
ConnectionHandler: inbound,
})Inbound Handler Pattern
AnyTLS uses the type-cast handler pattern (same as ShadowTLS). The Inbound type handles raw connections, while an inboundHandler type alias handles decoded connections:
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 Implementation
TLS Requirement
The outbound requires TLS:
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}TCP Fast Open Incompatibility
AnyTLS is explicitly incompatible with TCP Fast Open. TFO creates lazy connections that defer establishment until the first write, but AnyTLS needs the remote address during handshake:
if options.DialerOptions.TCPFastOpen {
return nil, E.New("tcp_fast_open is not supported with anytls outbound")
}Session Pooling
The client maintains a pool of idle TLS sessions for connection reuse. Session management is configurable:
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,
})Key session parameters:
- IdleSessionCheckInterval: How often to check for idle sessions
- IdleSessionTimeout: How long before an idle session is closed
- MinIdleSession: Minimum number of idle sessions to maintain in the pool
Dial Out Function
The DialOut callback creates new TLS connections for the session pool:
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
return h.dialer.DialTLSContext(ctx, h.server)
}TCP Connections via CreateProxy
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)
}
}UDP via UoT
UDP is supported through UDP-over-TCP using the uot package. The UoT client wraps the AnyTLS client's CreateProxy method:
outbound.uotClient = &uot.Client{
Dialer: (anytlsDialer)(client.CreateProxy),
Version: uot.Version,
}The anytlsDialer adapter converts the CreateProxy function signature to the N.Dialer interface:
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)
The inbound wraps its router with UoT support:
inbound.router = uot.NewRouter(router, logger)Configuration Examples
Inbound
{
"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
{
"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"
}
}