Add AnyTLS protocol

This commit is contained in:
anytls 2025-02-20 20:06:21 +08:00 committed by 世界
parent 7799b89e8d
commit c2a81243bf
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
15 changed files with 489 additions and 0 deletions

View file

@ -19,6 +19,7 @@ const (
TypeTor = "tor"
TypeSSH = "ssh"
TypeShadowTLS = "shadowtls"
TypeAnyTLS = "anytls"
TypeShadowsocksR = "shadowsocksr"
TypeVLESS = "vless"
TypeTUIC = "tuic"

View file

@ -0,0 +1,39 @@
### Structure
```json
{
"type": "anytls",
"tag": "anytls-in",
... // Listen Fields
"users": [
{
"name": "sekai",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
],
"padding_scheme": [],
"tls": {}
}
```
### Listen Fields
See [Listen Fields](/configuration/shared/listen/) for details.
### Fields
#### users
==Required==
AnyTLS users.
#### padding_scheme
AnyTLS padding scheme line array.
#### tls
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).

View file

@ -0,0 +1,39 @@
### 结构
```json
{
"type": "anytls",
"tag": "anytls-in",
... // 监听字段
"users": [
{
"name": "sekai",
"password": "8JCsPssfgS8tiRwiMlhARg=="
}
],
"padding_scheme": [],
"tls": {}
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### users
==必填==
AnyTLS 用户。
#### padding_scheme
AnyTLS 填充方案行数组。
#### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。

View file

@ -30,6 +30,7 @@
| `tuic` | [TUIC](./tuic/) | :material-close: |
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
| `vless` | [VLESS](./vless/) | TCP |
| `anytls` | [AnyTLS](./anytls/) | TCP |
| `tun` | [Tun](./tun/) | :material-close: |
| `redirect` | [Redirect](./redirect/) | :material-close: |
| `tproxy` | [TProxy](./tproxy/) | :material-close: |

View file

@ -30,6 +30,7 @@
| `tuic` | [TUIC](./tuic/) | :material-close: |
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
| `vless` | [VLESS](./vless/) | TCP |
| `anytls` | [AnyTLS](./anytls/) | TCP |
| `tun` | [Tun](./tun/) | :material-close: |
| `redirect` | [Redirect](./redirect/) | :material-close: |
| `tproxy` | [TProxy](./tproxy/) | :material-close: |

View file

@ -0,0 +1,55 @@
### Structure
```json
{
"type": "anytls",
"tag": "anytls-out",
"server": "127.0.0.1",
"server_port": 1080,
"password": "8JCsPssfgS8tiRwiMlhARg==",
"idle_session_check_interval": "30s",
"idle_session_timeout": "30s",
"tls": {},
... // Dial Fields
}
```
### Fields
#### server
==Required==
The server address.
#### server_port
==Required==
The server port.
#### password
==Required==
The AnyTLS password.
#### idle_session_check_interval
Interval checking for idle sessions. Default: 30s.
#### idle_session_timeout
In the check, close sessions that have been idle for longer than this. Default: 30s.
#### tls
==Required==
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View file

@ -0,0 +1,55 @@
### 结构
```json
{
"type": "anytls",
"tag": "anytls-out",
"server": "127.0.0.1",
"server_port": 1080,
"password": "8JCsPssfgS8tiRwiMlhARg==",
"idle_session_check_interval": "30s",
"idle_session_timeout": "30s",
"tls": {},
... // 拨号字段
}
```
### 字段
#### server
==必填==
服务器地址。
#### server_port
==必填==
服务器端口。
#### password
==必填==
AnyTLS 密码。
#### idle_session_check_interval
检查空闲会话的时间间隔。默认值:30秒。
#### idle_session_timeout
在检查中,关闭闲置时间超过此值的会话。默认值:30秒。
#### tls
==必填==
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/)。

View file

@ -30,6 +30,7 @@
| `shadowtls` | [ShadowTLS](./shadowtls/) |
| `tuic` | [TUIC](./tuic/) |
| `hysteria2` | [Hysteria2](./hysteria2/) |
| `anytls` | [AnyTLS](./anytls/) |
| `tor` | [Tor](./tor/) |
| `ssh` | [SSH](./ssh/) |
| `dns` | [DNS](./dns/) |

View file

@ -30,6 +30,7 @@
| `shadowtls` | [ShadowTLS](./shadowtls/) |
| `tuic` | [TUIC](./tuic/) |
| `hysteria2` | [Hysteria2](./hysteria2/) |
| `anytls` | [AnyTLS](./anytls/) |
| `tor` | [Tor](./tor/) |
| `ssh` | [SSH](./ssh/) |
| `dns` | [DNS](./dns/) |

1
go.mod
View file

@ -3,6 +3,7 @@ module github.com/sagernet/sing-box
go 1.23.1
require (
github.com/anytls/sing-anytls v0.0.2
github.com/caddyserver/certmagic v0.21.7
github.com/cloudflare/circl v1.6.0
github.com/cretz/bine v0.2.0

4
go.sum
View file

@ -8,6 +8,10 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anytls/sing-anytls v0.0.1 h1:Hex6GFUcgATWMWL2E9YgH/7oPgwdokiIF09UQi5BEC0=
github.com/anytls/sing-anytls v0.0.1/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
github.com/anytls/sing-anytls v0.0.2 h1:25azSh0o/LMcIkhS4ZutgRTIGwh8O3wuOhsThVM9K9o=
github.com/anytls/sing-anytls v0.0.2/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=

View file

@ -15,6 +15,7 @@ import (
"github.com/sagernet/sing-box/dns/transport/local"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/anytls"
"github.com/sagernet/sing-box/protocol/block"
"github.com/sagernet/sing-box/protocol/direct"
protocolDNS "github.com/sagernet/sing-box/protocol/dns"
@ -53,6 +54,7 @@ func InboundRegistry() *inbound.Registry {
naive.RegisterInbound(registry)
shadowtls.RegisterInbound(registry)
vless.RegisterInbound(registry)
anytls.RegisterInbound(registry)
registerQUICInbounds(registry)
registerStubForRemovedInbounds(registry)
@ -80,6 +82,7 @@ func OutboundRegistry() *outbound.Registry {
ssh.RegisterOutbound(registry)
shadowtls.RegisterOutbound(registry)
vless.RegisterOutbound(registry)
anytls.RegisterOutbound(registry)
registerQUICOutbounds(registry)
registerWireGuardOutbound(registry)

24
option/anytls.go Normal file
View file

@ -0,0 +1,24 @@
package option
import "github.com/sagernet/sing/common/json/badoption"
type AnyTLSInboundOptions struct {
ListenOptions
InboundTLSOptionsContainer
Users []AnyTLSUser `json:"users,omitempty"`
PaddingScheme badoption.Listable[string] `json:"padding_scheme,omitempty"`
}
type AnyTLSUser struct {
Name string `json:"name,omitempty"`
Password string `json:"password,omitempty"`
}
type AnyTLSOutboundOptions struct {
DialerOptions
ServerOptions
OutboundTLSOptionsContainer
Password string `json:"password,omitempty"`
IdleSessionCheckInterval badoption.Duration `json:"idle_session_check_interval,omitempty"`
IdleSessionTimeout badoption.Duration `json:"idle_session_timeout,omitempty"`
}

135
protocol/anytls/inbound.go Normal file
View file

@ -0,0 +1,135 @@
package anytls
import (
"context"
"net"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
anytls "github.com/anytls/sing-anytls"
"github.com/anytls/sing-anytls/padding"
)
func RegisterInbound(registry *inbound.Registry) {
inbound.Register[option.AnyTLSInboundOptions](registry, C.TypeAnyTLS, NewInbound)
}
type Inbound struct {
inbound.Adapter
tlsConfig tls.ServerConfig
router adapter.ConnectionRouterEx
logger logger.ContextLogger
listener *listener.Listener
service *anytls.Service
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSInboundOptions) (adapter.Inbound, error) {
inbound := &Inbound{
Adapter: inbound.NewAdapter(C.TypeAnyTLS, tag),
router: uot.NewRouter(router, logger),
logger: logger,
}
if options.TLS != nil && options.TLS.Enabled {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
inbound.tlsConfig = tlsConfig
}
paddingScheme := padding.DefaultPaddingScheme
if len(options.PaddingScheme) > 0 {
paddingScheme = []byte(strings.Join(options.PaddingScheme, "\n"))
}
service, err := 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,
})
if err != nil {
return nil, err
}
inbound.service = service
inbound.listener = listener.New(listener.Options{
Context: ctx,
Logger: logger,
Network: []string{N.NetworkTCP},
Listen: options.ListenOptions,
ConnectionHandler: inbound,
})
return inbound, nil
}
func (h *Inbound) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
if h.tlsConfig != nil {
err := h.tlsConfig.Start()
if err != nil {
return err
}
}
return h.listener.Start()
}
func (h *Inbound) Close() error {
return common.Close(h.listener, h.tlsConfig)
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
if h.tlsConfig != nil {
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": TLS handshake"))
return
}
conn = tlsConn
}
err := h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, metadata.Source, onClose)
if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err)
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
}
type inboundHandler Inbound
func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
var metadata adapter.InboundContext
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.Source = source
metadata.Destination = destination
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
metadata.User = userName
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
} else {
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
}
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}

129
protocol/anytls/outbound.go Normal file
View file

@ -0,0 +1,129 @@
package anytls
import (
"context"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot"
anytls "github.com/anytls/sing-anytls"
)
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.AnyTLSOutboundOptions](registry, C.TypeAnyTLS, NewOutbound)
}
type Outbound struct {
outbound.Adapter
dialer N.Dialer
server M.Socksaddr
tlsConfig tls.Config
client *anytls.Client
uotClient *uot.Client
logger log.ContextLogger
}
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) {
outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeAnyTLS, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
server: options.ServerOptions.Build(),
logger: logger,
}
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
outbound.tlsConfig = tlsConfig
outboundDialer, err := dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
})
if err != nil {
return nil, err
}
outbound.dialer = outboundDialer
client, err := anytls.NewClient(ctx, anytls.ClientConfig{
Password: options.Password,
IdleSessionCheckInterval: options.IdleSessionCheckInterval.Build(),
IdleSessionTimeout: options.IdleSessionTimeout.Build(),
DialOut: outbound.dialOut,
Logger: logger,
})
if err != nil {
return nil, err
}
outbound.client = client
outbound.uotClient = &uot.Client{
Dialer: (anytlsDialer)(client.CreateProxy),
Version: uot.Version,
}
return outbound, nil
}
type anytlsDialer func(ctx context.Context, destination M.Socksaddr) (net.Conn, error)
func (d anytlsDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return d(ctx, destination)
}
func (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return nil, os.ErrInvalid
}
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
conn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.server)
if err != nil {
return nil, err
}
tlsConn, err := tls.ClientHandshake(ctx, conn, h.tlsConfig)
if err != nil {
common.Close(tlsConn, conn)
return nil, err
}
return tlsConn, nil
}
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
return h.client.CreateProxy(ctx, destination)
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
return h.uotClient.DialContext(ctx, network, destination)
}
return nil, os.ErrInvalid
}
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
return h.uotClient.ListenPacket(ctx, destination)
}
func (h *Outbound) Close() error {
return common.Close(h.client)
}