Skip to content

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

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 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:

go
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:

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 Scheme

AnyTLS uses a configurable padding scheme to obfuscate traffic patterns. The scheme is defined as a multi-line string:

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-Only Listener

AnyTLS only supports TCP connections:

go
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:

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 Implementation

TLS Requirement

The outbound requires TLS:

go
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:

go
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:

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,
})

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:

go
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
    return h.dialer.DialTLSContext(ctx, h.server)
}

TCP Connections via CreateProxy

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)
    }
}

UDP via UoT

UDP is supported through UDP-over-TCP using the uot package. The UoT client wraps the AnyTLS client's CreateProxy method:

go
outbound.uotClient = &uot.Client{
    Dialer:  (anytlsDialer)(client.CreateProxy),
    Version: uot.Version,
}

The anytlsDialer adapter converts the CreateProxy function signature to the 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)

The inbound wraps its router with UoT support:

go
inbound.router = uot.NewRouter(router, logger)

Configuration Examples

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"
  }
}