mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Add reality TLS
This commit is contained in:
parent
6d8cc4409f
commit
5d6ecab128
4
Makefile
4
Makefile
@ -1,7 +1,7 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api
|
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
||||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
|
|||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return NewECHClient(router, serverAddress, options)
|
return NewECHClient(router, serverAddress, options)
|
||||||
|
} else if options.Reality != nil && options.Reality.Enabled {
|
||||||
|
return NewRealityClient(router, serverAddress, options)
|
||||||
} else if options.UTLS != nil && options.UTLS.Enabled {
|
} else if options.UTLS != nil && options.UTLS.Enabled {
|
||||||
return NewUTLSClient(router, serverAddress, options)
|
return NewUTLSClient(router, serverAddress, options)
|
||||||
} else {
|
} else {
|
||||||
@ -39,10 +41,13 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
||||||
tlsConn := config.Client(conn)
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err := tlsConn.HandshakeContext(ctx)
|
tlsConn, err := config.Client(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ type Config interface {
|
|||||||
NextProtos() []string
|
NextProtos() []string
|
||||||
SetNextProtos(nextProto []string)
|
SetNextProtos(nextProto []string)
|
||||||
Config() (*STDConfig, error)
|
Config() (*STDConfig, error)
|
||||||
Client(conn net.Conn) Conn
|
Client(conn net.Conn) (Conn, error)
|
||||||
Clone() Config
|
Clone() Config
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,12 @@ type ConfigWithSessionIDGenerator interface {
|
|||||||
type ServerConfig interface {
|
type ServerConfig interface {
|
||||||
Config
|
Config
|
||||||
adapter.Service
|
adapter.Service
|
||||||
Server(conn net.Conn) Conn
|
Server(conn net.Conn) (Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfigCompat interface {
|
||||||
|
ServerConfig
|
||||||
|
ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Conn interface {
|
type Conn interface {
|
||||||
|
@ -44,8 +44,8 @@ func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
|||||||
return nil, E.New("unsupported usage for ECH")
|
return nil, E.New("unsupported usage for ECH")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
|
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return &echConnWrapper{cftls.Client(conn, e.config)}
|
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Clone() Config {
|
func (e *ECHClientConfig) Clone() Config {
|
||||||
@ -76,6 +76,10 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *echConnWrapper) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
|
187
common/tls/reality_client.go
Normal file
187
common/tls/reality_client.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
//go:build with_utls
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/debug"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Config = (*RealityClientConfig)(nil)
|
||||||
|
|
||||||
|
type RealityClientConfig struct {
|
||||||
|
uClient *UTLSClientConfig
|
||||||
|
publicKey []byte
|
||||||
|
shortID []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
|
||||||
|
if options.UTLS == nil || !options.UTLS.Enabled {
|
||||||
|
return nil, E.New("uTLS is required by reality client")
|
||||||
|
}
|
||||||
|
|
||||||
|
uClient, err := NewUTLSClient(router, serverAddress, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode public_key")
|
||||||
|
}
|
||||||
|
if len(publicKey) != 32 {
|
||||||
|
return nil, E.New("invalid public_key")
|
||||||
|
}
|
||||||
|
shortID, err := hex.DecodeString(options.Reality.ShortID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode short_id")
|
||||||
|
}
|
||||||
|
if len(shortID) != 8 {
|
||||||
|
return nil, E.New("invalid short_id")
|
||||||
|
}
|
||||||
|
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
|
return e.uClient.ServerName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) SetServerName(serverName string) {
|
||||||
|
e.uClient.SetServerName(serverName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) NextProtos() []string {
|
||||||
|
return e.uClient.NextProtos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) SetNextProtos(nextProto []string) {
|
||||||
|
e.uClient.SetNextProtos(nextProto)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) Config() (*STDConfig, error) {
|
||||||
|
return nil, E.New("unsupported usage for reality")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
|
verifier := &realityVerifier{
|
||||||
|
serverName: e.uClient.ServerName(),
|
||||||
|
}
|
||||||
|
uConfig := e.uClient.config.Clone()
|
||||||
|
uConfig.InsecureSkipVerify = true
|
||||||
|
uConfig.SessionTicketsDisabled = true
|
||||||
|
uConfig.VerifyPeerCertificate = verifier.VerifyPeerCertificate
|
||||||
|
uConn := utls.UClient(conn, uConfig, e.uClient.id)
|
||||||
|
verifier.UConn = uConn
|
||||||
|
err := uConn.BuildHandshakeState()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hello := uConn.HandshakeState.Hello
|
||||||
|
hello.SessionId = make([]byte, 32)
|
||||||
|
copy(hello.Raw[39:], hello.SessionId)
|
||||||
|
|
||||||
|
var nowTime time.Time
|
||||||
|
if uConfig.Time != nil {
|
||||||
|
nowTime = uConfig.Time()
|
||||||
|
} else {
|
||||||
|
nowTime = time.Now()
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
||||||
|
|
||||||
|
hello.SessionId[0] = 1
|
||||||
|
hello.SessionId[1] = 7
|
||||||
|
hello.SessionId[2] = 5
|
||||||
|
copy(hello.SessionId[8:], e.shortID)
|
||||||
|
|
||||||
|
if debug.Enabled {
|
||||||
|
fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16])
|
||||||
|
}
|
||||||
|
|
||||||
|
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(e.publicKey)
|
||||||
|
if authKey == nil {
|
||||||
|
return nil, E.New("nil auth_key")
|
||||||
|
}
|
||||||
|
verifier.authKey = authKey
|
||||||
|
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aesBlock, _ := aes.NewCipher(authKey)
|
||||||
|
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
||||||
|
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||||||
|
copy(hello.Raw[39:], hello.SessionId)
|
||||||
|
if debug.Enabled {
|
||||||
|
fmt.Printf("REALITY hello.sessionId: %v\n", hello.SessionId)
|
||||||
|
fmt.Printf("REALITY uConn.AuthKey: %v\n", authKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &utlsConnWrapper{uConn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
|
e.uClient.config.SessionIDGenerator = generator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RealityClientConfig) Clone() Config {
|
||||||
|
return &RealityClientConfig{
|
||||||
|
e.uClient.Clone().(*UTLSClientConfig),
|
||||||
|
e.publicKey,
|
||||||
|
e.shortID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type realityVerifier struct {
|
||||||
|
*utls.UConn
|
||||||
|
serverName string
|
||||||
|
authKey []byte
|
||||||
|
verified bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
|
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||||
|
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
|
||||||
|
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
|
||||||
|
h := hmac.New(sha512.New, c.authKey)
|
||||||
|
h.Write(pub)
|
||||||
|
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
|
||||||
|
c.verified = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
DNSName: c.serverName,
|
||||||
|
Intermediates: x509.NewCertPool(),
|
||||||
|
}
|
||||||
|
for _, cert := range certs[1:] {
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
if _, err := certs[0].Verify(opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !c.verified {
|
||||||
|
return E.New("reality verification failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
194
common/tls/reality_server.go
Normal file
194
common/tls/reality_server.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
//go:build with_reality_server
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/debug"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/nekohasekai/reality"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
||||||
|
|
||||||
|
type RealityServerConfig struct {
|
||||||
|
config *reality.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
||||||
|
var tlsConfig reality.Config
|
||||||
|
|
||||||
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
|
return nil, E.New("acme is unavailable in reality")
|
||||||
|
}
|
||||||
|
tlsConfig.Time = router.TimeFunc()
|
||||||
|
if options.ServerName != "" {
|
||||||
|
tlsConfig.ServerName = options.ServerName
|
||||||
|
}
|
||||||
|
if len(options.ALPN) > 0 {
|
||||||
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||||
|
}
|
||||||
|
if options.MinVersion != "" {
|
||||||
|
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse min_version")
|
||||||
|
}
|
||||||
|
tlsConfig.MinVersion = minVersion
|
||||||
|
}
|
||||||
|
if options.MaxVersion != "" {
|
||||||
|
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse max_version")
|
||||||
|
}
|
||||||
|
tlsConfig.MaxVersion = maxVersion
|
||||||
|
}
|
||||||
|
if options.CipherSuites != nil {
|
||||||
|
find:
|
||||||
|
for _, cipherSuite := range options.CipherSuites {
|
||||||
|
for _, tlsCipherSuite := range tls.CipherSuites() {
|
||||||
|
if cipherSuite == tlsCipherSuite.Name {
|
||||||
|
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||||
|
continue find
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.Certificate != "" || options.CertificatePath != "" {
|
||||||
|
return nil, E.New("certificate is unavailable in reality")
|
||||||
|
}
|
||||||
|
if options.Key != "" || options.KeyPath != "" {
|
||||||
|
return nil, E.New("key is unavailable in reality")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig.SessionTicketsDisabled = true
|
||||||
|
tlsConfig.Type = N.NetworkTCP
|
||||||
|
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
||||||
|
|
||||||
|
tlsConfig.ServerNames = map[string]bool{options.ServerName: true}
|
||||||
|
privateKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode private key")
|
||||||
|
}
|
||||||
|
if len(privateKey) != 32 {
|
||||||
|
return nil, E.New("invalid private key")
|
||||||
|
}
|
||||||
|
tlsConfig.PrivateKey = privateKey
|
||||||
|
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
||||||
|
|
||||||
|
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
||||||
|
for i, shortID := range options.Reality.ShortID {
|
||||||
|
var shortIDBytesArray [8]byte
|
||||||
|
decodedLen, err := hex.Decode(shortIDBytesArray[:], []byte(shortID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortID)
|
||||||
|
}
|
||||||
|
if decodedLen != 8 {
|
||||||
|
return nil, E.New("invalid short_id[", i, "]: ", shortID)
|
||||||
|
}
|
||||||
|
tlsConfig.ShortIds[shortIDBytesArray] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
|
||||||
|
tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug.Enabled {
|
||||||
|
tlsConfig.Show = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RealityServerConfig{&tlsConfig}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) ServerName() string {
|
||||||
|
return c.config.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) SetServerName(serverName string) {
|
||||||
|
c.config.ServerName = serverName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) NextProtos() []string {
|
||||||
|
return c.config.NextProtos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) SetNextProtos(nextProto []string) {
|
||||||
|
c.config.NextProtos = nextProto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Config() (*tls.Config, error) {
|
||||||
|
return nil, E.New("unsupported usage for reality")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
||||||
|
tlsConn, err := reality.Server(ctx, conn, c.config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &realityConnWrapper{Conn: tlsConn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RealityServerConfig) Clone() Config {
|
||||||
|
return &RealityServerConfig{
|
||||||
|
config: c.config.Clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Conn = (*realityConnWrapper)(nil)
|
||||||
|
|
||||||
|
type realityConnWrapper struct {
|
||||||
|
*reality.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||||
|
state := c.Conn.ConnectionState()
|
||||||
|
return tls.ConnectionState{
|
||||||
|
Version: state.Version,
|
||||||
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
|
DidResume: state.DidResume,
|
||||||
|
CipherSuite: state.CipherSuite,
|
||||||
|
NegotiatedProtocol: state.NegotiatedProtocol,
|
||||||
|
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
||||||
|
ServerName: state.ServerName,
|
||||||
|
PeerCertificates: state.PeerCertificates,
|
||||||
|
VerifiedChains: state.VerifiedChains,
|
||||||
|
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
||||||
|
OCSPResponse: state.OCSPResponse,
|
||||||
|
TLSUnique: state.TLSUnique,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityConnWrapper) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
16
common/tls/reality_stub.go
Normal file
16
common/tls/reality_stub.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//go:build !with_reality_server
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
|
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
||||||
|
}
|
@ -16,14 +16,24 @@ func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, op
|
|||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
if options.Reality != nil && options.Reality.Enabled {
|
||||||
|
return NewRealityServer(ctx, router, logger, options)
|
||||||
|
} else {
|
||||||
return NewSTDServer(ctx, router, logger, options)
|
return NewSTDServer(ctx, router, logger, options)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
||||||
tlsConn := config.Server(conn)
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err := tlsConn.HandshakeContext(ctx)
|
if compatServer, isCompat := config.(ServerConfigCompat); isCompat {
|
||||||
|
return compatServer.ServerHandshake(ctx, conn)
|
||||||
|
}
|
||||||
|
tlsConn, err := config.Server(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tlsConn.HandshakeContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@ func (s *STDClientConfig) Config() (*STDConfig, error) {
|
|||||||
return s.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Client(conn net.Conn) Conn {
|
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return tls.Client(conn, s.config)
|
return tls.Client(conn, s.config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
|
@ -48,12 +48,12 @@ func (c *STDServerConfig) Config() (*STDConfig, error) {
|
|||||||
return c.config, nil
|
return c.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Client(conn net.Conn) Conn {
|
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return tls.Client(conn, c.config)
|
return tls.Client(conn, c.config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Server(conn net.Conn) Conn {
|
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||||
return tls.Server(conn, c.config)
|
return tls.Server(conn, c.config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Clone() Config {
|
func (c *STDServerConfig) Clone() Config {
|
||||||
|
@ -40,14 +40,21 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
|||||||
return nil, E.New("unsupported usage for uTLS")
|
return nil, E.New("unsupported usage for uTLS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
|
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
e.config.SessionIDGenerator = generator
|
e.config.SessionIDGenerator = generator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *UTLSClientConfig) Clone() Config {
|
||||||
|
return &UTLSClientConfig{
|
||||||
|
config: e.config.Clone(),
|
||||||
|
id: e.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
type utlsConnWrapper struct {
|
||||||
*utls.UConn
|
*utls.UConn
|
||||||
}
|
}
|
||||||
@ -70,14 +77,11 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Clone() Config {
|
func (c *utlsConnWrapper) Upstream() any {
|
||||||
return &UTLSClientConfig{
|
return c.UConn
|
||||||
config: e.config.Clone(),
|
|
||||||
id: e.id,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@ -148,28 +152,34 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
var id utls.ClientHelloID
|
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
|
||||||
switch options.UTLS.Fingerprint {
|
if err != nil {
|
||||||
case "chrome", "":
|
return nil, err
|
||||||
id = utls.HelloChrome_Auto
|
|
||||||
case "firefox":
|
|
||||||
id = utls.HelloFirefox_Auto
|
|
||||||
case "edge":
|
|
||||||
id = utls.HelloEdge_Auto
|
|
||||||
case "safari":
|
|
||||||
id = utls.HelloSafari_Auto
|
|
||||||
case "360":
|
|
||||||
id = utls.Hello360_Auto
|
|
||||||
case "qq":
|
|
||||||
id = utls.HelloQQ_Auto
|
|
||||||
case "ios":
|
|
||||||
id = utls.HelloIOS_Auto
|
|
||||||
case "android":
|
|
||||||
id = utls.HelloAndroid_11_OkHttp
|
|
||||||
case "random":
|
|
||||||
id = utls.HelloRandomized
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
|
|
||||||
}
|
}
|
||||||
return &UTLSClientConfig{&tlsConfig, id}, nil
|
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||||
|
switch name {
|
||||||
|
case "chrome", "":
|
||||||
|
return utls.HelloChrome_Auto, nil
|
||||||
|
case "firefox":
|
||||||
|
return utls.HelloFirefox_Auto, nil
|
||||||
|
case "edge":
|
||||||
|
return utls.HelloEdge_Auto, nil
|
||||||
|
case "safari":
|
||||||
|
return utls.HelloSafari_Auto, nil
|
||||||
|
case "360":
|
||||||
|
return utls.Hello360_Auto, nil
|
||||||
|
case "qq":
|
||||||
|
return utls.HelloQQ_Auto, nil
|
||||||
|
case "ios":
|
||||||
|
return utls.HelloIOS_Auto, nil
|
||||||
|
case "android":
|
||||||
|
return utls.HelloAndroid_11_OkHttp, nil
|
||||||
|
case "random":
|
||||||
|
return utls.HelloRandomized, nil
|
||||||
|
default:
|
||||||
|
return utls.ClientHelloID{}, E.New("unknown uTLS fingerprint: ", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,3 +11,7 @@ import (
|
|||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
|
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
|
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
||||||
|
}
|
||||||
|
3
go.mod
3
go.mod
@ -18,6 +18,7 @@ require (
|
|||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mholt/acmez v1.1.0
|
github.com/mholt/acmez v1.1.0
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.50
|
||||||
|
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0
|
github.com/oschwald/maxminddb-golang v1.10.0
|
||||||
github.com/pires/go-proxyproto v0.6.2
|
github.com/pires/go-proxyproto v0.6.2
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
||||||
@ -31,7 +32,7 @@ require (
|
|||||||
github.com/sagernet/sing-vmess v0.1.2
|
github.com/sagernet/sing-vmess v0.1.2
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
|
6
go.sum
6
go.sum
@ -92,6 +92,8 @@ github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
|
|||||||
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||||
|
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd h1:vd4qbG9ZTW10e1uqo8PDLshe5XL2yPhdINhGlJYaOoQ=
|
||||||
|
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd/go.mod h1:C+iqSNDBQ8qMhlNZ0JSUO9POEWq8qX87hukGfmO7/fA=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||||
@ -143,8 +145,8 @@ github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT
|
|||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056 h1:gDXi/0uYe8dA48UyUI1LM2la5QYN0IvsDvR2H2+kFnA=
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01 h1:m4MI13+NRKddIvbdSN0sFHK8w5ROTa60Zi9diZ7EE08=
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
|
@ -13,6 +13,7 @@ type InboundTLSOptions struct {
|
|||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||||
|
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTLSOptions struct {
|
type OutboundTLSOptions struct {
|
||||||
@ -28,6 +29,20 @@ type OutboundTLSOptions struct {
|
|||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
||||||
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
||||||
|
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundRealityOptions struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"`
|
||||||
|
PrivateKey string `json:"private_key,omitempty"`
|
||||||
|
ShortID Listable[string] `json:"short_id,omitempty"`
|
||||||
|
MaxTimeDifference Duration `json:"max_time_difference,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundRealityHandshakeOptions struct {
|
||||||
|
ServerOptions
|
||||||
|
DialerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundECHOptions struct {
|
type OutboundECHOptions struct {
|
||||||
@ -41,3 +56,9 @@ type OutboundUTLSOptions struct {
|
|||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundRealityOptions struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
PublicKey string `json:"public_key,omitempty"`
|
||||||
|
ShortID string `json:"short_id,omitempty"`
|
||||||
|
}
|
||||||
|
@ -58,7 +58,6 @@ var allImages = []string{
|
|||||||
var localIP = netip.MustParseAddr("127.0.0.1")
|
var localIP = netip.MustParseAddr("127.0.0.1")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -18,6 +18,8 @@ require (
|
|||||||
golang.org/x/net v0.7.0
|
golang.org/x/net v0.7.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/xtls/reality => github.com/nekohasekai/reality v0.0.0-20230225043811-04070a6bdbea
|
||||||
|
|
||||||
require (
|
require (
|
||||||
berty.tech/go-libtor v1.0.385 // indirect
|
berty.tech/go-libtor v1.0.385 // indirect
|
||||||
github.com/Dreamacro/clash v1.13.0 // indirect
|
github.com/Dreamacro/clash v1.13.0 // indirect
|
||||||
@ -51,6 +53,7 @@ require (
|
|||||||
github.com/miekg/dns v1.1.50 // indirect
|
github.com/miekg/dns v1.1.50 // indirect
|
||||||
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect
|
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
|
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
@ -73,7 +76,7 @@ require (
|
|||||||
github.com/sagernet/sing-vmess v0.1.2 // indirect
|
github.com/sagernet/sing-vmess v0.1.2 // indirect
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d // indirect
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d // indirect
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056 // indirect
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01 // indirect
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
||||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
|
@ -104,6 +104,8 @@ github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c h1:RC8WMpjonrBfyAh6VN/PO
|
|||||||
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c/go.mod h1:9OcmHNQQUTbk4XCffrLgN1NEKc2mh5u++biHVrvHsSU=
|
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c/go.mod h1:9OcmHNQQUTbk4XCffrLgN1NEKc2mh5u++biHVrvHsSU=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd h1:vd4qbG9ZTW10e1uqo8PDLshe5XL2yPhdINhGlJYaOoQ=
|
||||||
|
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd/go.mod h1:C+iqSNDBQ8qMhlNZ0JSUO9POEWq8qX87hukGfmO7/fA=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||||
@ -156,8 +158,8 @@ github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT
|
|||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056 h1:gDXi/0uYe8dA48UyUI1LM2la5QYN0IvsDvR2H2+kFnA=
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01 h1:m4MI13+NRKddIvbdSN0sFHK8w5ROTa60Zi9diZ7EE08=
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
|
@ -319,7 +319,6 @@ func testVLESSSelfTLS(t *testing.T, flow string) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
||||||
Type: C.TypeTrojan,
|
Type: C.TypeTrojan,
|
||||||
Tag: "trojan",
|
Tag: "trojan",
|
||||||
TrojanOptions: option.TrojanInboundOptions{
|
TrojanOptions: option.TrojanInboundOptions{
|
||||||
@ -396,3 +395,134 @@ func testVLESSSelfTLS(t *testing.T, flow string) {
|
|||||||
})
|
})
|
||||||
testSuit(t, clientPort, testPort)
|
testSuit(t, clientPort, testPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVLESSVisionReality(t *testing.T) {
|
||||||
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
|
|
||||||
|
userUUID := newUUID()
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeMixed,
|
||||||
|
Tag: "mixed-in",
|
||||||
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: clientPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeVLESS,
|
||||||
|
VLESSOptions: option.VLESSInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: serverPort,
|
||||||
|
},
|
||||||
|
Users: []option.VLESSUser{
|
||||||
|
{
|
||||||
|
Name: "sekai",
|
||||||
|
UUID: userUUID.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &option.InboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "google.com",
|
||||||
|
Reality: &option.InboundRealityOptions{
|
||||||
|
Enabled: true,
|
||||||
|
Handshake: option.InboundRealityHandshakeOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "google.com",
|
||||||
|
ServerPort: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ShortID: []string{"0123456789abcdef"},
|
||||||
|
PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeTrojan,
|
||||||
|
Tag: "trojan",
|
||||||
|
TrojanOptions: option.TrojanInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: otherPort,
|
||||||
|
},
|
||||||
|
Users: []option.TrojanUser{
|
||||||
|
{
|
||||||
|
Name: "sekai",
|
||||||
|
Password: userUUID.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &option.InboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
CertificatePath: certPem,
|
||||||
|
KeyPath: keyPem,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbounds: []option.Outbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeDirect,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeTrojan,
|
||||||
|
Tag: "trojan-out",
|
||||||
|
TrojanOptions: option.TrojanOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
ServerPort: otherPort,
|
||||||
|
},
|
||||||
|
Password: userUUID.String(),
|
||||||
|
TLS: &option.OutboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
CertificatePath: certPem,
|
||||||
|
},
|
||||||
|
DialerOptions: option.DialerOptions{
|
||||||
|
Detour: "vless-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeVLESS,
|
||||||
|
Tag: "vless-out",
|
||||||
|
VLESSOptions: option.VLESSOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
ServerPort: serverPort,
|
||||||
|
},
|
||||||
|
UUID: userUUID.String(),
|
||||||
|
Flow: vless.FlowVision,
|
||||||
|
TLS: &option.OutboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "google.com",
|
||||||
|
Reality: &option.OutboundRealityOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ShortID: "0123456789abcdef",
|
||||||
|
PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
||||||
|
},
|
||||||
|
UTLS: &option.OutboundUTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: &option.RouteOptions{
|
||||||
|
Rules: []option.Rule{
|
||||||
|
{
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
Inbound: []string{"mixed-in"},
|
||||||
|
Outbound: "trojan-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testSuit(t, clientPort, testPort)
|
||||||
|
}
|
||||||
|
@ -36,7 +36,7 @@ func (c *Client) prepareConn(conn net.Conn) (net.Conn, error) {
|
|||||||
if c.flow == FlowVision {
|
if c.flow == FlowVision {
|
||||||
vConn, err := NewVisionConn(conn, c.key)
|
vConn, err := NewVisionConn(conn, c.key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, E.Cause(err, "initialize vision")
|
||||||
}
|
}
|
||||||
conn = vConn
|
conn = vConn
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,20 @@ import (
|
|||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var tlsRegistry []func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer uintptr)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer uintptr) {
|
||||||
|
tlsConn, loaded := conn.(*tls.Conn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn).Elem(), uintptr(unsafe.Pointer(tlsConn))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type VisionConn struct {
|
type VisionConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
writer N.VectorisedWriter
|
writer N.VectorisedWriter
|
||||||
@ -46,18 +57,19 @@ type VisionConn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewVisionConn(conn net.Conn, userUUID [16]byte) (*VisionConn, error) {
|
func NewVisionConn(conn net.Conn, userUUID [16]byte) (*VisionConn, error) {
|
||||||
var reflectType reflect.Type
|
var (
|
||||||
var reflectPointer uintptr
|
loaded bool
|
||||||
var netConn net.Conn
|
reflectType reflect.Type
|
||||||
if tlsConn, ok := conn.(*tls.Conn); ok {
|
reflectPointer uintptr
|
||||||
netConn = tlsConn.NetConn()
|
netConn net.Conn
|
||||||
reflectType = reflect.TypeOf(tlsConn).Elem()
|
)
|
||||||
reflectPointer = uintptr(unsafe.Pointer(tlsConn))
|
for _, tlsCreator := range tlsRegistry {
|
||||||
} else if uConn, ok := conn.(*utls.UConn); ok {
|
loaded, netConn, reflectType, reflectPointer = tlsCreator(conn)
|
||||||
netConn = uConn.NetConn()
|
if loaded {
|
||||||
reflectType = reflect.TypeOf(uConn).Elem()
|
break
|
||||||
reflectPointer = uintptr(unsafe.Pointer(uConn))
|
}
|
||||||
} else {
|
}
|
||||||
|
if !loaded {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
input, _ := reflectType.FieldByName("input")
|
input, _ := reflectType.FieldByName("input")
|
23
transport/vless/vision_reality.go
Normal file
23
transport/vless/vision_reality.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
//go:build with_reality_server
|
||||||
|
|
||||||
|
package vless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
|
||||||
|
"github.com/nekohasekai/reality"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer uintptr) {
|
||||||
|
tlsConn, loaded := common.Cast[*reality.Conn](conn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn).Elem(), uintptr(unsafe.Pointer(tlsConn))
|
||||||
|
})
|
||||||
|
}
|
22
transport/vless/vision_utls.go
Normal file
22
transport/vless/vision_utls.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//go:build with_utls
|
||||||
|
|
||||||
|
package vless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer uintptr) {
|
||||||
|
tlsConn, loaded := common.Cast[*utls.UConn](conn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), uintptr(unsafe.Pointer(tlsConn.Conn))
|
||||||
|
})
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user