mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Implement AnyTLS client & server
This commit is contained in:
parent
63a0b5e2ce
commit
103684ffeb
@ -19,6 +19,7 @@ const (
|
|||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
|
TypeAnyTLS = "anytls"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module github.com/sagernet/sing-box
|
|||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/anytls/sing-anytls v0.0.0-20250220052249-d1e5da3f7f1c
|
||||||
github.com/caddyserver/certmagic v0.21.7
|
github.com/caddyserver/certmagic v0.21.7
|
||||||
github.com/cloudflare/circl v1.6.0
|
github.com/cloudflare/circl v1.6.0
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
|
2
go.sum
2
go.sum
@ -8,6 +8,8 @@ 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/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 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
github.com/anytls/sing-anytls v0.0.0-20250220052249-d1e5da3f7f1c h1:wpJMhczSiijsxNiH5fLYWt0Od7oYrFuz9znb4aaZ4Hc=
|
||||||
|
github.com/anytls/sing-anytls v0.0.0-20250220052249-d1e5da3f7f1c/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 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"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/block"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
protocolDNS "github.com/sagernet/sing-box/protocol/dns"
|
protocolDNS "github.com/sagernet/sing-box/protocol/dns"
|
||||||
@ -53,6 +54,7 @@ func InboundRegistry() *inbound.Registry {
|
|||||||
naive.RegisterInbound(registry)
|
naive.RegisterInbound(registry)
|
||||||
shadowtls.RegisterInbound(registry)
|
shadowtls.RegisterInbound(registry)
|
||||||
vless.RegisterInbound(registry)
|
vless.RegisterInbound(registry)
|
||||||
|
anytls.RegisterInbound(registry)
|
||||||
|
|
||||||
registerQUICInbounds(registry)
|
registerQUICInbounds(registry)
|
||||||
registerStubForRemovedInbounds(registry)
|
registerStubForRemovedInbounds(registry)
|
||||||
@ -80,6 +82,7 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
ssh.RegisterOutbound(registry)
|
ssh.RegisterOutbound(registry)
|
||||||
shadowtls.RegisterOutbound(registry)
|
shadowtls.RegisterOutbound(registry)
|
||||||
vless.RegisterOutbound(registry)
|
vless.RegisterOutbound(registry)
|
||||||
|
anytls.RegisterOutbound(registry)
|
||||||
|
|
||||||
registerQUICOutbounds(registry)
|
registerQUICOutbounds(registry)
|
||||||
registerWireGuardOutbound(registry)
|
registerWireGuardOutbound(registry)
|
||||||
|
24
option/anytls.go
Normal file
24
option/anytls.go
Normal 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"`
|
||||||
|
}
|
134
protocol/anytls/inbound.go
Normal file
134
protocol/anytls/inbound.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package anytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
anytls "github.com/anytls/sing-anytls"
|
||||||
|
"github.com/anytls/sing-anytls/padding"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
118
protocol/anytls/outbound.go
Normal file
118
protocol/anytls/outbound.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package anytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
anytls "github.com/anytls/sing-anytls"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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}, 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: outbound,
|
||||||
|
Version: uot.Version,
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user