mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 08:31:30 +00:00
Add VLESS server, vision flow and reality TLS
This commit is contained in:
parent
e766f25d55
commit
fbc94b9e3e
4
Makefile
4
Makefile
|
@ -1,7 +1,7 @@
|
|||
NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
||||
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_reality_server,with_shadowsocksr
|
||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||
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 {
|
||||
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 {
|
||||
return NewUTLSClient(router, serverAddress, options)
|
||||
} 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) {
|
||||
tlsConn := config.Client(conn)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||
defer cancel()
|
||||
err := tlsConn.HandshakeContext(ctx)
|
||||
tlsConn, err := config.Client(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ type Config interface {
|
|||
NextProtos() []string
|
||||
SetNextProtos(nextProto []string)
|
||||
Config() (*STDConfig, error)
|
||||
Client(conn net.Conn) Conn
|
||||
Client(conn net.Conn) (Conn, error)
|
||||
Clone() Config
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,12 @@ type ConfigWithSessionIDGenerator interface {
|
|||
type ServerConfig interface {
|
||||
Config
|
||||
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 {
|
||||
|
|
|
@ -44,8 +44,8 @@ func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
|||
return nil, E.New("unsupported usage for ECH")
|
||||
}
|
||||
|
||||
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
|
||||
return &echConnWrapper{cftls.Client(conn, e.config)}
|
||||
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
||||
}
|
||||
|
||||
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) {
|
||||
var serverName string
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
return NewSTDServer(ctx, router, logger, options)
|
||||
if options.Reality != nil && options.Reality.Enabled {
|
||||
return NewRealityServer(ctx, router, logger, options)
|
||||
} else {
|
||||
return NewSTDServer(ctx, router, logger, options)
|
||||
}
|
||||
}
|
||||
|
||||
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
||||
tlsConn := config.Server(conn)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ func (s *STDClientConfig) Config() (*STDConfig, error) {
|
|||
return s.config, nil
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) Client(conn net.Conn) Conn {
|
||||
return tls.Client(conn, s.config)
|
||||
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return tls.Client(conn, s.config), nil
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) Clone() Config {
|
||||
|
|
|
@ -48,12 +48,12 @@ func (c *STDServerConfig) Config() (*STDConfig, error) {
|
|||
return c.config, nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Client(conn net.Conn) Conn {
|
||||
return tls.Client(conn, c.config)
|
||||
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return tls.Client(conn, c.config), nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Server(conn net.Conn) Conn {
|
||||
return tls.Server(conn, c.config)
|
||||
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||
return tls.Server(conn, c.config), nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Clone() Config {
|
||||
|
|
|
@ -40,14 +40,21 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
|||
return nil, E.New("unsupported usage for uTLS")
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
|
||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
|
||||
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||
e.config.SessionIDGenerator = generator
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) Clone() Config {
|
||||
return &UTLSClientConfig{
|
||||
config: e.config.Clone(),
|
||||
id: e.id,
|
||||
}
|
||||
}
|
||||
|
||||
type utlsConnWrapper struct {
|
||||
*utls.UConn
|
||||
}
|
||||
|
@ -70,14 +77,11 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
|||
}
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) Clone() Config {
|
||||
return &UTLSClientConfig{
|
||||
config: e.config.Clone(),
|
||||
id: e.id,
|
||||
}
|
||||
func (c *utlsConnWrapper) Upstream() any {
|
||||
return c.UConn
|
||||
}
|
||||
|
||||
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
|
||||
if options.ServerName != "" {
|
||||
serverName = options.ServerName
|
||||
|
@ -148,28 +152,34 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
}
|
||||
var id utls.ClientHelloID
|
||||
switch options.UTLS.Fingerprint {
|
||||
case "chrome", "":
|
||||
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)
|
||||
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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) {
|
||||
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`)
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
| `naive` | [Naive](./naive) | X |
|
||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
|
||||
| `vless` | [VLESS](./vless) | TCP |
|
||||
| `tun` | [Tun](./tun) | X |
|
||||
| `redirect` | [Redirect](./redirect) | X |
|
||||
| `tproxy` | [TProxy](./tproxy) | X |
|
||||
|
|
39
docs/configuration/inbound/vless.md
Normal file
39
docs/configuration/inbound/vless.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
### Fields
|
||||
|
||||
#### users
|
||||
|
||||
==Required==
|
||||
|
||||
VLESS users.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
39
docs/configuration/inbound/vless.zh.md
Normal file
39
docs/configuration/inbound/vless.zh.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### users
|
||||
|
||||
==必填==
|
||||
|
||||
VLESS 用户。
|
||||
|
||||
#### tls
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
|
@ -8,6 +8,7 @@
|
|||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
|
@ -17,10 +18,6 @@
|
|||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
The VLESS protocol is architecturally coupled to v2ray and is unmaintained. This outbound is provided for compatibility purposes only.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
@ -41,6 +38,14 @@ The server port.
|
|||
|
||||
The VLESS user id.
|
||||
|
||||
#### flow
|
||||
|
||||
VLESS Sub-protocol.
|
||||
|
||||
Available values:
|
||||
|
||||
* `xtls-rprx-vision`
|
||||
|
||||
#### network
|
||||
|
||||
Enabled network
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
|
@ -17,10 +18,6 @@
|
|||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
VLESS 协议与 v2ray 架构耦合且无人维护。 提供此出站仅出于兼容性目的。
|
||||
|
||||
### 字段
|
||||
|
||||
#### server
|
||||
|
@ -41,6 +38,14 @@
|
|||
|
||||
VLESS 用户 ID。
|
||||
|
||||
#### flow
|
||||
|
||||
VLESS 子协议。
|
||||
|
||||
可用值:
|
||||
|
||||
* `xtls-rprx-vision`
|
||||
|
||||
#### network
|
||||
|
||||
启用的网络协议。
|
||||
|
|
|
@ -26,6 +26,20 @@
|
|||
"key_id": "",
|
||||
"mac_key": ""
|
||||
}
|
||||
},
|
||||
"reality": {
|
||||
"enabled": false,
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // Dial Fields
|
||||
},
|
||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
||||
"short_id": [
|
||||
"0123456789abcdef"
|
||||
],
|
||||
"max_time_difference": "1m"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -53,6 +67,11 @@
|
|||
"utls": {
|
||||
"enabled": false,
|
||||
"fingerprint": ""
|
||||
},
|
||||
"reality": {
|
||||
"enabled": false,
|
||||
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
||||
"short_id": "0123456789abcdef"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -275,6 +294,54 @@ The key identifier.
|
|||
|
||||
The MAC key.
|
||||
|
||||
### Reality Fields
|
||||
|
||||
!!! warning ""
|
||||
|
||||
reality server is not included by default, see [Installation](/#installation).
|
||||
|
||||
!!! warning ""
|
||||
|
||||
uTLS, which is required by reality client is not included by default, see [Installation](/#installation).
|
||||
|
||||
#### handshake
|
||||
|
||||
==Server only==
|
||||
|
||||
==Required==
|
||||
|
||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||
|
||||
#### private_key
|
||||
|
||||
==Server only==
|
||||
|
||||
==Required==
|
||||
|
||||
Private key, generated by `./xray x25519`.
|
||||
|
||||
#### public_key
|
||||
|
||||
==Client only==
|
||||
|
||||
==Required==
|
||||
|
||||
Public key, generated by `./xray x25519`.
|
||||
|
||||
#### short_id
|
||||
|
||||
==Required==
|
||||
|
||||
A 8-bit hex string.
|
||||
|
||||
#### max_time_difference
|
||||
|
||||
==Server only==
|
||||
|
||||
The maximum time difference between the server and the client.
|
||||
|
||||
Check disabled if empty.
|
||||
|
||||
### Reload
|
||||
|
||||
For server configuration, certificate and key will be automatically reloaded if modified.
|
|
@ -26,6 +26,20 @@
|
|||
"key_id": "",
|
||||
"mac_key": ""
|
||||
}
|
||||
},
|
||||
"reality": {
|
||||
"enabled": false,
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // 拨号字段
|
||||
},
|
||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
||||
"short_id": [
|
||||
"0123456789abcdef"
|
||||
],
|
||||
"max_time_difference": "1m"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -53,6 +67,11 @@
|
|||
"utls": {
|
||||
"enabled": false,
|
||||
"fingerprint": ""
|
||||
},
|
||||
"reality": {
|
||||
"enabled": false,
|
||||
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
||||
"short_id": "0123456789abcdef"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -271,6 +290,52 @@ EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到其他已知
|
|||
|
||||
MAC 密钥。
|
||||
|
||||
### Reality 字段
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含 reality 服务器,参阅 [安装](/zh/#_2)。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含被 reality 客户端需要的 uTLS, 参阅 [安装](/zh/#_2)。
|
||||
|
||||
#### handshake
|
||||
|
||||
==仅服务器==
|
||||
|
||||
==必填==
|
||||
|
||||
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||
|
||||
#### private_key
|
||||
|
||||
==仅服务器==
|
||||
|
||||
==必填==
|
||||
|
||||
私钥,由 `./xray x25519` 生成。
|
||||
|
||||
#### public_key
|
||||
|
||||
==仅客户端==
|
||||
|
||||
==必填==
|
||||
|
||||
公钥,由 `./xray x25519` 生成。
|
||||
|
||||
#### short_id
|
||||
|
||||
==必填==
|
||||
|
||||
一个八位十六进制的字符串。
|
||||
|
||||
#### max_time_difference
|
||||
|
||||
服务器与和客户端之间允许的最大时间差。
|
||||
|
||||
默认禁用检查。
|
||||
|
||||
### 重载
|
||||
|
||||
对于服务器配置,如果修改,证书和密钥将自动重新加载。
|
3
go.mod
3
go.mod
|
@ -18,6 +18,7 @@ require (
|
|||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mholt/acmez v1.1.0
|
||||
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/pires/go-proxyproto v0.6.2
|
||||
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/smux v0.0.0-20220831015742-e0f1988e3195
|
||||
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/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
||||
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/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
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/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
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/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/utls v0.0.0-20230220130002-c08891932056 h1:gDXi/0uYe8dA48UyUI1LM2la5QYN0IvsDvR2H2+kFnA=
|
||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01 h1:m4MI13+NRKddIvbdSN0sFHK8w5ROTa60Zi9diZ7EE08=
|
||||
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/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||
|
|
|
@ -42,6 +42,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
|||
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
|
||||
case C.TypeShadowTLS:
|
||||
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
||||
case C.TypeVLESS:
|
||||
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
||||
default:
|
||||
return nil, E.New("unknown inbound type: ", options.Type)
|
||||
}
|
||||
|
|
193
inbound/vless.go
Normal file
193
inbound/vless.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"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-box/transport/v2ray"
|
||||
"github.com/sagernet/sing-box/transport/vless"
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/auth"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var (
|
||||
_ adapter.Inbound = (*VLESS)(nil)
|
||||
_ adapter.InjectableInbound = (*VLESS)(nil)
|
||||
)
|
||||
|
||||
type VLESS struct {
|
||||
myInboundAdapter
|
||||
ctx context.Context
|
||||
users []option.VLESSUser
|
||||
service *vless.Service[int]
|
||||
tlsConfig tls.ServerConfig
|
||||
transport adapter.V2RayServerTransport
|
||||
}
|
||||
|
||||
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (*VLESS, error) {
|
||||
inbound := &VLESS{
|
||||
myInboundAdapter: myInboundAdapter{
|
||||
protocol: C.TypeVLESS,
|
||||
network: []string{N.NetworkTCP},
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
},
|
||||
ctx: ctx,
|
||||
users: options.Users,
|
||||
}
|
||||
service := vless.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound))
|
||||
service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int {
|
||||
return index
|
||||
}), common.Map(inbound.users, func(it option.VLESSUser) string {
|
||||
return it.UUID
|
||||
}))
|
||||
inbound.service = service
|
||||
var err error
|
||||
if options.TLS != nil {
|
||||
inbound.tlsConfig, err = tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.Transport != nil {
|
||||
inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create server transport: ", options.Transport.Type)
|
||||
}
|
||||
}
|
||||
inbound.connHandler = inbound
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
func (h *VLESS) Start() error {
|
||||
err := common.Start(
|
||||
h.service,
|
||||
h.tlsConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.transport == nil {
|
||||
return h.myInboundAdapter.Start()
|
||||
}
|
||||
if common.Contains(h.transport.Network(), N.NetworkTCP) {
|
||||
tcpListener, err := h.myInboundAdapter.ListenTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
sErr := h.transport.Serve(tcpListener)
|
||||
if sErr != nil && !E.IsClosed(sErr) {
|
||||
h.logger.Error("transport serve error: ", sErr)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if common.Contains(h.transport.Network(), N.NetworkUDP) {
|
||||
udpConn, err := h.myInboundAdapter.ListenUDP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
sErr := h.transport.ServePacket(udpConn)
|
||||
if sErr != nil && !E.IsClosed(sErr) {
|
||||
h.logger.Error("transport serve error: ", sErr)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *VLESS) Close() error {
|
||||
return common.Close(
|
||||
h.service,
|
||||
&h.myInboundAdapter,
|
||||
h.tlsConfig,
|
||||
h.transport,
|
||||
)
|
||||
}
|
||||
|
||||
func (h *VLESS) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
h.injectTCP(conn, metadata)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
var err error
|
||||
if h.tlsConfig != nil && h.transport == nil {
|
||||
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||
}
|
||||
|
||||
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||
if !loaded {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
user := h.users[userIndex].Name
|
||||
if user == "" {
|
||||
user = F.ToString(userIndex)
|
||||
} else {
|
||||
metadata.User = user
|
||||
}
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||
return h.router.RouteConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
||||
if !loaded {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
user := h.users[userIndex].Name
|
||||
if user == "" {
|
||||
user = F.ToString(userIndex)
|
||||
} else {
|
||||
metadata.User = user
|
||||
}
|
||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||
}
|
||||
return h.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil)
|
||||
|
||||
type vlessTransportHandler VLESS
|
||||
|
||||
func (t *vlessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
return (*VLESS)(t).newTransportConnection(ctx, conn, adapter.InboundContext{
|
||||
Source: metadata.Source,
|
||||
Destination: metadata.Destination,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *vlessTransportHandler) FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
return os.ErrInvalid
|
||||
}
|
|
@ -71,6 +71,7 @@ nav:
|
|||
- Naive: configuration/inbound/naive.md
|
||||
- Hysteria: configuration/inbound/hysteria.md
|
||||
- ShadowTLS: configuration/inbound/shadowtls.md
|
||||
- VLESS: configuration/inbound/vless.md
|
||||
- Tun: configuration/inbound/tun.md
|
||||
- Redirect: configuration/inbound/redirect.md
|
||||
- TProxy: configuration/inbound/tproxy.md
|
||||
|
|
|
@ -22,6 +22,7 @@ type _Inbound struct {
|
|||
NaiveOptions NaiveInboundOptions `json:"-"`
|
||||
HysteriaOptions HysteriaInboundOptions `json:"-"`
|
||||
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
|
||||
VLESSOptions VLESSInboundOptions `json:"-"`
|
||||
}
|
||||
|
||||
type Inbound _Inbound
|
||||
|
@ -55,6 +56,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
|
|||
v = h.HysteriaOptions
|
||||
case C.TypeShadowTLS:
|
||||
v = h.ShadowTLSOptions
|
||||
case C.TypeVLESS:
|
||||
v = h.VLESSOptions
|
||||
default:
|
||||
return nil, E.New("unknown inbound type: ", h.Type)
|
||||
}
|
||||
|
@ -94,6 +97,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
|||
v = &h.HysteriaOptions
|
||||
case C.TypeShadowTLS:
|
||||
v = &h.ShadowTLSOptions
|
||||
case C.TypeVLESS:
|
||||
v = &h.VLESSOptions
|
||||
default:
|
||||
return E.New("unknown inbound type: ", h.Type)
|
||||
}
|
||||
|
|
|
@ -1,33 +1,48 @@
|
|||
package option
|
||||
|
||||
type InboundTLSOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN Listable[string] `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
KeyPath string `json:"key_path,omitempty"`
|
||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN Listable[string] `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
KeyPath string `json:"key_path,omitempty"`
|
||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundTLSOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN Listable[string] `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
||||
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
DisableSNI bool `json:"disable_sni,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
ALPN Listable[string] `json:"alpn,omitempty"`
|
||||
MinVersion string `json:"min_version,omitempty"`
|
||||
MaxVersion string `json:"max_version,omitempty"`
|
||||
CipherSuites Listable[string] `json:"cipher_suites,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
ECH *OutboundECHOptions `json:"ech,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 {
|
||||
|
@ -41,3 +56,9 @@ type OutboundUTLSOptions struct {
|
|||
Enabled bool `json:"enabled,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"`
|
||||
}
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
package option
|
||||
|
||||
type VLESSInboundOptions struct {
|
||||
ListenOptions
|
||||
Users []VLESSUser `json:"users,omitempty"`
|
||||
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
}
|
||||
|
||||
type VLESSUser struct {
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
type VLESSOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
UUID string `json:"uuid"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||
|
|
|
@ -11,9 +11,9 @@ import (
|
|||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2ray"
|
||||
"github.com/sagernet/sing-box/transport/vless"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
"github.com/sagernet/sing-vmess/vless"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
@ -67,7 +67,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||
default:
|
||||
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
||||
}
|
||||
outbound.client, err = vless.NewClient(options.UUID)
|
||||
outbound.client, err = vless.NewClient(options.UUID, options.Flow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -92,16 +92,12 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S
|
|||
return nil, err
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
case N.NetworkUDP:
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||
return h.client.DialEarlyConn(conn, destination), nil
|
||||
return h.client.DialEarlyConn(conn, destination)
|
||||
case N.NetworkUDP:
|
||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||
return h.client.DialEarlyPacketConn(conn, destination)
|
||||
default:
|
||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||
}
|
||||
|
@ -126,11 +122,15 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
|
|||
return nil, err
|
||||
}
|
||||
if h.xudp {
|
||||
return h.client.DialEarlyXUDPPacketConn(conn, destination), nil
|
||||
return h.client.DialEarlyXUDPPacketConn(conn, destination)
|
||||
} else if h.packetAddr {
|
||||
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
|
||||
conn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(conn, destination)), nil
|
||||
} else {
|
||||
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||
return h.client.DialEarlyPacketConn(conn, destination)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
@ -59,14 +58,6 @@ var allImages = []string{
|
|||
var localIP = netip.MustParseAddr("127.0.0.1")
|
||||
|
||||
func init() {
|
||||
if C.IsDarwin {
|
||||
var err error
|
||||
localIP, err = defaultRouteIP()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
39
test/config/vless-tls-server.json
Normal file
39
test/config/vless-tls-server.json
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "0.0.0.0",
|
||||
"port": 1234,
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"decryption": "none",
|
||||
"clients": [
|
||||
{
|
||||
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
"flow": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"serverName": "example.org",
|
||||
"certificates": [
|
||||
{
|
||||
"certificateFile": "/path/to/certificate.crt",
|
||||
"keyFile": "/path/to/private.key"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -18,6 +18,8 @@ require (
|
|||
golang.org/x/net v0.7.0
|
||||
)
|
||||
|
||||
replace github.com/xtls/reality => github.com/nekohasekai/reality v0.0.0-20230225043811-04070a6bdbea
|
||||
|
||||
require (
|
||||
berty.tech/go-libtor v1.0.385 // indirect
|
||||
github.com/Dreamacro/clash v1.13.0 // indirect
|
||||
|
@ -51,6 +53,7 @@ require (
|
|||
github.com/miekg/dns v1.1.50 // indirect
|
||||
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // 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/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
|
@ -68,12 +71,12 @@ require (
|
|||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 // indirect
|
||||
github.com/sagernet/sing-dns v0.1.4 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221075551-c5ad05179260 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587 // indirect
|
||||
github.com/sagernet/sing-tun v0.1.1 // indirect
|
||||
github.com/sagernet/sing-vmess v0.1.2 // 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/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/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
|
|
10
test/go.sum
10
test/go.sum
|
@ -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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
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/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
|
@ -146,8 +148,8 @@ github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN
|
|||
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
|
||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0=
|
||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9/go.mod h1:f3mHTy5shnVM9l8UocMlJgC/1G/zdj5FuEuVXhDinGU=
|
||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221075551-c5ad05179260 h1:RKeyBMI5kRnno3/WGsW4HrGnZkhISQQrnRxAKXbf5Vg=
|
||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221075551-c5ad05179260/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587 h1:OjIXlHT2bblZfp+ciupM4xY9+Ccpj9FsuHRtKRBv+Pg=
|
||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
||||
github.com/sagernet/sing-tun v0.1.1 h1:2Hg3GAyJWzQ7Ua1j74dE+mI06vaqSBO9yD4tkTjggn4=
|
||||
github.com/sagernet/sing-tun v0.1.1/go.mod h1:WzW/SkT+Nh9uJn/FIYUE2YJHYuPwfbh8sATOzU9QDGw=
|
||||
github.com/sagernet/sing-vmess v0.1.2 h1:RbOZNAId2LrCai8epMoQXlf0XTrou0bfcw08hNBg6lM=
|
||||
|
@ -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/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/utls v0.0.0-20230220130002-c08891932056 h1:gDXi/0uYe8dA48UyUI1LM2la5QYN0IvsDvR2H2+kFnA=
|
||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01 h1:m4MI13+NRKddIvbdSN0sFHK8w5ROTa60Zi9diZ7EE08=
|
||||
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/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/vless"
|
||||
|
||||
"github.com/spyzhov/ajson"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -28,8 +29,9 @@ func TestVLESS(t *testing.T) {
|
|||
|
||||
startDockerContainer(t, DockerOptions{
|
||||
Image: ImageV2RayCore,
|
||||
Ports: []uint16{serverPort, testPort},
|
||||
Ports: []uint16{serverPort},
|
||||
EntryPoint: "v2ray",
|
||||
Cmd: []string{"run"},
|
||||
Stdin: content,
|
||||
})
|
||||
|
||||
|
@ -58,36 +60,48 @@ func TestVLESS(t *testing.T) {
|
|||
},
|
||||
},
|
||||
})
|
||||
testSuit(t, clientPort, testPort)
|
||||
testTCP(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func TestVLESSXRay(t *testing.T) {
|
||||
testVLESSXray(t, "")
|
||||
t.Run("origin", func(t *testing.T) {
|
||||
testVLESSXray(t, "", "")
|
||||
})
|
||||
t.Run("xudp", func(t *testing.T) {
|
||||
testVLESSXray(t, "xudp", "")
|
||||
})
|
||||
t.Run("vision", func(t *testing.T) {
|
||||
testVLESSXray(t, "", vless.FlowVision)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVLESSXUDP(t *testing.T) {
|
||||
testVLESSXray(t, "xudp")
|
||||
}
|
||||
func testVLESSXray(t *testing.T, packetEncoding string, flow string) {
|
||||
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||
|
||||
func testVLESSXray(t *testing.T, packetEncoding string) {
|
||||
content, err := os.ReadFile("config/vless-server.json")
|
||||
content, err := os.ReadFile("config/vless-tls-server.json")
|
||||
require.NoError(t, err)
|
||||
config, err := ajson.Unmarshal(content)
|
||||
require.NoError(t, err)
|
||||
|
||||
user := newUUID()
|
||||
userID := newUUID()
|
||||
inbound := config.MustKey("inbounds").MustIndex(0)
|
||||
inbound.MustKey("port").SetNumeric(float64(serverPort))
|
||||
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(user.String())
|
||||
user := inbound.MustKey("settings").MustKey("clients").MustIndex(0)
|
||||
user.MustKey("id").SetString(userID.String())
|
||||
user.MustKey("flow").SetString(flow)
|
||||
|
||||
content, err = ajson.Marshal(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
startDockerContainer(t, DockerOptions{
|
||||
Image: ImageXRayCore,
|
||||
Ports: []uint16{serverPort, testPort},
|
||||
Ports: []uint16{serverPort},
|
||||
EntryPoint: "xray",
|
||||
Stdin: content,
|
||||
Bind: map[string]string{
|
||||
certPem: "/path/to/certificate.crt",
|
||||
keyPem: "/path/to/private.key",
|
||||
},
|
||||
})
|
||||
|
||||
startInstance(t, option.Options{
|
||||
|
@ -101,17 +115,411 @@ func testVLESSXray(t *testing.T, packetEncoding string) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.TypeTrojan,
|
||||
Tag: "trojan",
|
||||
TrojanOptions: option.TrojanInboundOptions{
|
||||
ListenOptions: option.ListenOptions{
|
||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||
ListenPort: otherPort,
|
||||
},
|
||||
Users: []option.TrojanUser{
|
||||
{
|
||||
Name: "sekai",
|
||||
Password: userID.String(),
|
||||
},
|
||||
},
|
||||
TLS: &option.InboundTLSOptions{
|
||||
Enabled: true,
|
||||
ServerName: "example.org",
|
||||
CertificatePath: certPem,
|
||||
KeyPath: keyPem,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Outbounds: []option.Outbound{
|
||||
{
|
||||
Type: C.TypeTrojan,
|
||||
TrojanOptions: option.TrojanOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "host.docker.internal",
|
||||
ServerPort: otherPort,
|
||||
},
|
||||
Password: userID.String(),
|
||||
TLS: &option.OutboundTLSOptions{
|
||||
Enabled: true,
|
||||
ServerName: "example.org",
|
||||
CertificatePath: certPem,
|
||||
},
|
||||
DialerOptions: option.DialerOptions{
|
||||
Detour: "vless",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.TypeVLESS,
|
||||
Tag: "vless",
|
||||
VLESSOptions: option.VLESSOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
UUID: user.String(),
|
||||
UUID: userID.String(),
|
||||
Flow: flow,
|
||||
PacketEncoding: packetEncoding,
|
||||
TLS: &option.OutboundTLSOptions{
|
||||
Enabled: true,
|
||||
ServerName: "example.org",
|
||||
CertificatePath: certPem,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.TypeDirect,
|
||||
Tag: "direct",
|
||||
},
|
||||
},
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Inbound: []string{"trojan"},
|
||||
Outbound: "direct",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
testTCP(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func TestVLESSSelf(t *testing.T) {
|
||||
t.Run("origin", func(t *testing.T) {
|
||||
testVLESSSelf(t, "")
|
||||
})
|
||||
t.Run("vision", func(t *testing.T) {
|
||||
testVLESSSelf(t, vless.FlowVision)
|
||||
})
|
||||
t.Run("vision-tls", func(t *testing.T) {
|
||||
testVLESSSelfTLS(t, vless.FlowVision)
|
||||
})
|
||||
}
|
||||
|
||||
func testVLESSSelf(t *testing.T, flow string) {
|
||||
_, 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: "example.org",
|
||||
CertificatePath: certPem,
|
||||
KeyPath: keyPem,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Outbounds: []option.Outbound{
|
||||
{
|
||||
Type: C.TypeDirect,
|
||||
},
|
||||
{
|
||||
Type: C.TypeVLESS,
|
||||
Tag: "vless-out",
|
||||
VLESSOptions: option.VLESSOutboundOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: "127.0.0.1",
|
||||
ServerPort: serverPort,
|
||||
},
|
||||
UUID: userUUID.String(),
|
||||
Flow: flow,
|
||||
TLS: &option.OutboundTLSOptions{
|
||||
Enabled: true,
|
||||
ServerName: "example.org",
|
||||
CertificatePath: certPem,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Inbound: []string{"mixed-in"},
|
||||
Outbound: "vless-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
testSuit(t, clientPort, testPort)
|
||||
}
|
||||
|
||||
func testVLESSSelfTLS(t *testing.T, flow string) {
|
||||
_, 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: "example.org",
|
||||
CertificatePath: certPem,
|
||||
KeyPath: keyPem,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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: flow,
|
||||
TLS: &option.OutboundTLSOptions{
|
||||
Enabled: true,
|
||||
ServerName: "example.org",
|
||||
CertificatePath: certPem,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Inbound: []string{"mixed-in"},
|
||||
Outbound: "trojan-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
204
transport/vless/client.go
Normal file
204
transport/vless/client.go
Normal file
|
@ -0,0 +1,204 @@
|
|||
package vless
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
key [16]byte
|
||||
flow string
|
||||
}
|
||||
|
||||
func NewClient(userId string, flow string) (*Client, error) {
|
||||
user := uuid.FromStringOrNil(userId)
|
||||
if user == uuid.Nil {
|
||||
user = uuid.NewV5(user, userId)
|
||||
}
|
||||
switch flow {
|
||||
case "", "xtls-rprx-vision":
|
||||
default:
|
||||
return nil, E.New("unsupported flow: " + flow)
|
||||
}
|
||||
return &Client{user, flow}, nil
|
||||
}
|
||||
|
||||
func (c *Client) prepareConn(conn net.Conn) (net.Conn, error) {
|
||||
if c.flow == FlowVision {
|
||||
vConn, err := NewVisionConn(conn, c.key)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize vision")
|
||||
}
|
||||
conn = vConn
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialConn(conn net.Conn, destination M.Socksaddr) (*Conn, error) {
|
||||
vConn, err := c.prepareConn(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverConn := &Conn{Conn: conn, protocolConn: vConn, key: c.key, command: vmess.CommandTCP, destination: destination, flow: c.flow}
|
||||
return serverConn, common.Error(serverConn.Write(nil))
|
||||
}
|
||||
|
||||
func (c *Client) DialEarlyConn(conn net.Conn, destination M.Socksaddr) (*Conn, error) {
|
||||
vConn, err := c.prepareConn(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Conn{Conn: conn, protocolConn: vConn, key: c.key, command: vmess.CommandTCP, destination: destination, flow: c.flow}, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialPacketConn(conn net.Conn, destination M.Socksaddr) (*PacketConn, error) {
|
||||
serverConn := &PacketConn{Conn: conn, key: c.key, destination: destination, flow: c.flow}
|
||||
return serverConn, common.Error(serverConn.Write(nil))
|
||||
}
|
||||
|
||||
func (c *Client) DialEarlyPacketConn(conn net.Conn, destination M.Socksaddr) (*PacketConn, error) {
|
||||
return &PacketConn{Conn: conn, key: c.key, destination: destination, flow: c.flow}, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) (vmess.PacketConn, error) {
|
||||
serverConn := &Conn{Conn: conn, protocolConn: conn, key: c.key, command: vmess.CommandMux, destination: destination, flow: c.flow}
|
||||
err := common.Error(serverConn.Write(nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vmess.NewXUDPConn(serverConn, destination), nil
|
||||
}
|
||||
|
||||
func (c *Client) DialEarlyXUDPPacketConn(conn net.Conn, destination M.Socksaddr) (vmess.PacketConn, error) {
|
||||
return vmess.NewXUDPConn(&Conn{Conn: conn, protocolConn: conn, key: c.key, command: vmess.CommandMux, destination: destination, flow: c.flow}, destination), nil
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
protocolConn net.Conn
|
||||
key [16]byte
|
||||
command byte
|
||||
destination M.Socksaddr
|
||||
flow string
|
||||
requestWritten bool
|
||||
responseRead bool
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||
if !c.responseRead {
|
||||
err = ReadResponse(c.Conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.responseRead = true
|
||||
}
|
||||
return c.protocolConn.Read(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
if !c.requestWritten {
|
||||
request := Request{c.key, c.command, c.destination, c.flow}
|
||||
if c.protocolConn != nil {
|
||||
err = WriteRequest(c.Conn, request, nil)
|
||||
} else {
|
||||
err = WriteRequest(c.Conn, request, b)
|
||||
}
|
||||
if err == nil {
|
||||
n = len(b)
|
||||
}
|
||||
c.requestWritten = true
|
||||
if c.protocolConn == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return c.protocolConn.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
type PacketConn struct {
|
||||
net.Conn
|
||||
key [16]byte
|
||||
destination M.Socksaddr
|
||||
flow string
|
||||
requestWritten bool
|
||||
responseRead bool
|
||||
}
|
||||
|
||||
func (c *PacketConn) Read(b []byte) (n int, err error) {
|
||||
if !c.responseRead {
|
||||
err = ReadResponse(c.Conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.responseRead = true
|
||||
}
|
||||
var length uint16
|
||||
err = binary.Read(c.Conn, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if cap(b) < int(length) {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
return io.ReadFull(c.Conn, b[:length])
|
||||
}
|
||||
|
||||
func (c *PacketConn) Write(b []byte) (n int, err error) {
|
||||
if !c.requestWritten {
|
||||
err = WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination, c.flow}, nil)
|
||||
if err == nil {
|
||||
n = len(b)
|
||||
}
|
||||
c.requestWritten = true
|
||||
}
|
||||
err = binary.Write(c.Conn, binary.BigEndian, uint16(len(b)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
defer buffer.Release()
|
||||
dataLen := buffer.Len()
|
||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(dataLen))
|
||||
if !c.requestWritten {
|
||||
err := WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination, c.flow}, buffer.Bytes())
|
||||
c.requestWritten = true
|
||||
return err
|
||||
}
|
||||
return common.Error(c.Conn.Write(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, err = c.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr = c.destination.UDPAddr()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return c.Write(p)
|
||||
}
|
||||
|
||||
func (c *PacketConn) FrontHeadroom() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
34
transport/vless/constant.go
Normal file
34
transport/vless/constant.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package vless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
)
|
||||
|
||||
var (
|
||||
tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}
|
||||
tlsClientHandShakeStart = []byte{0x16, 0x03}
|
||||
tlsServerHandShakeStart = []byte{0x16, 0x03, 0x03}
|
||||
tlsApplicationDataStart = []byte{0x17, 0x03, 0x03}
|
||||
)
|
||||
|
||||
var tls13CipherSuiteDic = map[uint16]string{
|
||||
0x1301: "TLS_AES_128_GCM_SHA256",
|
||||
0x1302: "TLS_AES_256_GCM_SHA384",
|
||||
0x1303: "TLS_CHACHA20_POLY1305_SHA256",
|
||||
0x1304: "TLS_AES_128_CCM_SHA256",
|
||||
0x1305: "TLS_AES_128_CCM_8_SHA256",
|
||||
}
|
||||
|
||||
func reshapeBuffer(b []byte) []*buf.Buffer {
|
||||
const bufferLimit = 8192 - 21
|
||||
if len(b) < bufferLimit {
|
||||
return []*buf.Buffer{buf.As(b)}
|
||||
}
|
||||
index := int32(bytes.LastIndex(b, tlsApplicationDataStart))
|
||||
if index <= 0 {
|
||||
index = 8192 / 2
|
||||
}
|
||||
return []*buf.Buffer{buf.As(b[:index]), buf.As(b[index:])}
|
||||
}
|
241
transport/vless/protocol.go
Normal file
241
transport/vless/protocol.go
Normal file
|
@ -0,0 +1,241 @@
|
|||
package vless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = 0
|
||||
FlowVision = "xtls-rprx-vision"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
UUID [16]byte
|
||||
Command byte
|
||||
Destination M.Socksaddr
|
||||
Flow string
|
||||
}
|
||||
|
||||
func ReadRequest(reader io.Reader) (*Request, error) {
|
||||
var request Request
|
||||
|
||||
var version uint8
|
||||
err := binary.Read(reader, binary.BigEndian, &version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != Version {
|
||||
return nil, E.New("unknown version: ", version)
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(reader, request.UUID[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addonsLen uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &addonsLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if addonsLen > 0 {
|
||||
addonsBytes, err := rw.ReadBytes(reader, int(addonsLen))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addons, err := readAddons(bytes.NewReader(addonsBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Flow = addons.Flow
|
||||
}
|
||||
|
||||
err = binary.Read(reader, binary.BigEndian, &request.Command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if request.Command != vmess.CommandMux {
|
||||
request.Destination, err = vmess.AddressSerializer.ReadAddrPort(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &request, nil
|
||||
}
|
||||
|
||||
type Addons struct {
|
||||
Flow string
|
||||
Seed string
|
||||
}
|
||||
|
||||
func readAddons(reader io.Reader) (*Addons, error) {
|
||||
protoHeader, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if protoHeader != 10 {
|
||||
return nil, E.New("unknown protobuf message header: ", protoHeader)
|
||||
}
|
||||
|
||||
var addons Addons
|
||||
|
||||
flowLen, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return &addons, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
flowBytes, err := rw.ReadBytes(reader, int(flowLen))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addons.Flow = string(flowBytes)
|
||||
|
||||
seedLen, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return &addons, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
seedBytes, err := rw.ReadBytes(reader, int(seedLen))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addons.Seed = string(seedBytes)
|
||||
|
||||
return &addons, nil
|
||||
}
|
||||
|
||||
func WriteRequest(writer io.Writer, request Request, payload []byte) error {
|
||||
var requestLen int
|
||||
requestLen += 1 // version
|
||||
requestLen += 16 // uuid
|
||||
requestLen += 1 // protobuf length
|
||||
|
||||
var addonsLen int
|
||||
if request.Flow != "" {
|
||||
addonsLen += 1 // protobuf header
|
||||
addonsLen += UvarintLen(uint64(len(request.Flow)))
|
||||
addonsLen += len(request.Flow)
|
||||
requestLen += addonsLen
|
||||
}
|
||||
requestLen += 1 // command
|
||||
if request.Command != vmess.CommandMux {
|
||||
requestLen += vmess.AddressSerializer.AddrPortLen(request.Destination)
|
||||
}
|
||||
requestLen += len(payload)
|
||||
_buffer := buf.StackNewSize(requestLen)
|
||||
defer common.KeepAlive(_buffer)
|
||||
buffer := common.Dup(_buffer)
|
||||
defer buffer.Release()
|
||||
common.Must(
|
||||
buffer.WriteByte(Version),
|
||||
common.Error(buffer.Write(request.UUID[:])),
|
||||
buffer.WriteByte(byte(addonsLen)),
|
||||
)
|
||||
if addonsLen > 0 {
|
||||
common.Must(buffer.WriteByte(10))
|
||||
binary.PutUvarint(buffer.Extend(UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
|
||||
common.Must(common.Error(buffer.Write([]byte(request.Flow))))
|
||||
}
|
||||
common.Must(
|
||||
buffer.WriteByte(request.Command),
|
||||
)
|
||||
|
||||
if request.Command != vmess.CommandMux {
|
||||
common.Must(vmess.AddressSerializer.WriteAddrPort(buffer, request.Destination))
|
||||
}
|
||||
|
||||
common.Must1(buffer.Write(payload))
|
||||
return common.Error(writer.Write(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func WritePacketRequest(writer io.Writer, request Request, payload []byte) error {
|
||||
var requestLen int
|
||||
requestLen += 1 // version
|
||||
requestLen += 16 // uuid
|
||||
requestLen += 1 // protobuf length
|
||||
var addonsLen int
|
||||
/*if request.Flow != "" {
|
||||
addonsLen += 1 // protobuf header
|
||||
addonsLen += UvarintLen(uint64(len(request.Flow)))
|
||||
addonsLen += len(request.Flow)
|
||||
requestLen += addonsLen
|
||||
}*/
|
||||
requestLen += 1 // command
|
||||
requestLen += vmess.AddressSerializer.AddrPortLen(request.Destination)
|
||||
if len(payload) > 0 {
|
||||
requestLen += 2
|
||||
requestLen += len(payload)
|
||||
}
|
||||
_buffer := buf.StackNewSize(requestLen)
|
||||
defer common.KeepAlive(_buffer)
|
||||
buffer := common.Dup(_buffer)
|
||||
defer buffer.Release()
|
||||
common.Must(
|
||||
buffer.WriteByte(Version),
|
||||
common.Error(buffer.Write(request.UUID[:])),
|
||||
buffer.WriteByte(byte(addonsLen)),
|
||||
)
|
||||
|
||||
if addonsLen > 0 {
|
||||
common.Must(buffer.WriteByte(10))
|
||||
binary.PutUvarint(buffer.Extend(UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
|
||||
common.Must(common.Error(buffer.Write([]byte(request.Flow))))
|
||||
}
|
||||
|
||||
common.Must(
|
||||
buffer.WriteByte(vmess.CommandUDP),
|
||||
vmess.AddressSerializer.WriteAddrPort(buffer, request.Destination),
|
||||
)
|
||||
|
||||
if len(payload) > 0 {
|
||||
common.Must(
|
||||
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
||||
common.Error(buffer.Write(payload)),
|
||||
)
|
||||
}
|
||||
|
||||
return common.Error(writer.Write(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func ReadResponse(reader io.Reader) error {
|
||||
version, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version != Version {
|
||||
return E.New("unknown version: ", version)
|
||||
}
|
||||
protobufLength, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if protobufLength > 0 {
|
||||
err = rw.SkipN(reader, int(protobufLength))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UvarintLen(value uint64) int {
|
||||
var buffer [binary.MaxVarintLen64]byte
|
||||
return binary.PutUvarint(buffer[:], value)
|
||||
}
|
196
transport/vless/service.go
Normal file
196
transport/vless/service.go
Normal file
|
@ -0,0 +1,196 @@
|
|||
package vless
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common/auth"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type Service[T any] struct {
|
||||
userMap map[[16]byte]T
|
||||
handler Handler
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
N.TCPConnectionHandler
|
||||
N.UDPConnectionHandler
|
||||
E.Handler
|
||||
}
|
||||
|
||||
func NewService[T any](handler Handler) *Service[T] {
|
||||
return &Service[T]{
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service[T]) UpdateUsers(userList []T, userUUIDList []string) {
|
||||
userMap := make(map[[16]byte]T)
|
||||
for i, userName := range userList {
|
||||
userID := uuid.FromStringOrNil(userUUIDList[i])
|
||||
if userID == uuid.Nil {
|
||||
userID = uuid.NewV5(uuid.Nil, userUUIDList[i])
|
||||
}
|
||||
userMap[userID] = userName
|
||||
}
|
||||
s.userMap = userMap
|
||||
}
|
||||
|
||||
var _ N.TCPConnectionHandler = (*Service[int])(nil)
|
||||
|
||||
func (s *Service[T]) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
request, err := ReadRequest(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user, loaded := s.userMap[request.UUID]
|
||||
if !loaded {
|
||||
return E.New("unknown UUID: ", uuid.FromBytesOrNil(request.UUID[:]))
|
||||
}
|
||||
ctx = auth.ContextWithUser(ctx, user)
|
||||
metadata.Destination = request.Destination
|
||||
|
||||
protocolConn := conn
|
||||
switch request.Flow {
|
||||
case "":
|
||||
case FlowVision:
|
||||
protocolConn, err = NewVisionConn(conn, request.UUID)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize vision")
|
||||
}
|
||||
}
|
||||
|
||||
switch request.Command {
|
||||
case vmess.CommandTCP:
|
||||
return s.handler.NewConnection(ctx, &serverConn{Conn: protocolConn, responseWriter: conn}, metadata)
|
||||
case vmess.CommandUDP:
|
||||
return s.handler.NewPacketConnection(ctx, &serverPacketConn{ExtendedConn: bufio.NewExtendedConn(conn), destination: request.Destination}, metadata)
|
||||
case vmess.CommandMux:
|
||||
return vmess.HandleMuxConnection(ctx, &serverConn{Conn: conn}, s.handler)
|
||||
default:
|
||||
return E.New("unknown command: ", request.Command)
|
||||
}
|
||||
}
|
||||
|
||||
type serverConn struct {
|
||||
net.Conn
|
||||
responseWriter io.Writer
|
||||
responseWritten bool
|
||||
}
|
||||
|
||||
func (c *serverConn) Read(b []byte) (n int, err error) {
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *serverConn) Write(b []byte) (n int, err error) {
|
||||
if !c.responseWritten {
|
||||
if c.responseWriter == nil {
|
||||
_, err = bufio.WriteVectorised(bufio.NewVectorisedWriter(c.Conn), [][]byte{{Version, 0}, b})
|
||||
if err == nil {
|
||||
n = len(b)
|
||||
}
|
||||
c.responseWritten = true
|
||||
return
|
||||
} else {
|
||||
_, err = c.responseWriter.Write([]byte{Version, 0})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.responseWritten = true
|
||||
}
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
type serverPacketConn struct {
|
||||
N.ExtendedConn
|
||||
responseWriter io.Writer
|
||||
responseWritten bool
|
||||
destination M.Socksaddr
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
n, err = c.ExtendedConn.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr = c.destination.UDPAddr()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
if !c.responseWritten {
|
||||
if c.responseWriter == nil {
|
||||
var packetLen [2]byte
|
||||
binary.BigEndian.PutUint16(packetLen[:], uint16(len(p)))
|
||||
_, err = bufio.WriteVectorised(bufio.NewVectorisedWriter(c.ExtendedConn), [][]byte{{Version, 0}, packetLen[:], p})
|
||||
if err == nil {
|
||||
n = len(p)
|
||||
}
|
||||
c.responseWritten = true
|
||||
return
|
||||
} else {
|
||||
_, err = c.responseWriter.Write([]byte{Version, 0})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.responseWritten = true
|
||||
}
|
||||
}
|
||||
return c.ExtendedConn.Write(p)
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||
var packetLen uint16
|
||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &packetLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(packetLen))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
destination = c.destination
|
||||
return
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
if !c.responseWritten {
|
||||
if c.responseWriter == nil {
|
||||
var packetLen [2]byte
|
||||
binary.BigEndian.PutUint16(packetLen[:], uint16(buffer.Len()))
|
||||
err := bufio.NewVectorisedWriter(c.ExtendedConn).WriteVectorised([]*buf.Buffer{buf.As([]byte{Version, 0}), buf.As(packetLen[:]), buffer})
|
||||
c.responseWritten = true
|
||||
return err
|
||||
} else {
|
||||
_, err := c.responseWriter.Write([]byte{Version, 0})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.responseWritten = true
|
||||
}
|
||||
}
|
||||
packetLen := buffer.Len()
|
||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(packetLen))
|
||||
return c.ExtendedConn.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) FrontHeadroom() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (c *serverPacketConn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
309
transport/vless/vision.go
Normal file
309
transport/vless/vision.go
Normal file
|
@ -0,0 +1,309 @@
|
|||
package vless
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
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 {
|
||||
net.Conn
|
||||
writer N.VectorisedWriter
|
||||
input *bytes.Reader
|
||||
rawInput *bytes.Buffer
|
||||
netConn net.Conn
|
||||
|
||||
userUUID [16]byte
|
||||
isTLS bool
|
||||
numberOfPacketToFilter int
|
||||
isTLS12orAbove bool
|
||||
remainingServerHello int32
|
||||
cipher uint16
|
||||
enableXTLS bool
|
||||
filterTlsApplicationData bool
|
||||
directWrite bool
|
||||
writeUUID bool
|
||||
filterUUID bool
|
||||
remainingContent int
|
||||
remainingPadding int
|
||||
currentCommand int
|
||||
directRead bool
|
||||
remainingReader io.Reader
|
||||
}
|
||||
|
||||
func NewVisionConn(conn net.Conn, userUUID [16]byte) (*VisionConn, error) {
|
||||
var (
|
||||
loaded bool
|
||||
reflectType reflect.Type
|
||||
reflectPointer uintptr
|
||||
netConn net.Conn
|
||||
)
|
||||
for _, tlsCreator := range tlsRegistry {
|
||||
loaded, netConn, reflectType, reflectPointer = tlsCreator(conn)
|
||||
if loaded {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !loaded {
|
||||
return nil, C.ErrTLSRequired
|
||||
}
|
||||
input, _ := reflectType.FieldByName("input")
|
||||
rawInput, _ := reflectType.FieldByName("rawInput")
|
||||
return &VisionConn{
|
||||
Conn: conn,
|
||||
writer: bufio.NewVectorisedWriter(conn),
|
||||
input: (*bytes.Reader)(unsafe.Pointer(reflectPointer + input.Offset)),
|
||||
rawInput: (*bytes.Buffer)(unsafe.Pointer(reflectPointer + rawInput.Offset)),
|
||||
netConn: netConn,
|
||||
userUUID: userUUID,
|
||||
numberOfPacketToFilter: 8,
|
||||
remainingServerHello: -1,
|
||||
filterTlsApplicationData: true,
|
||||
writeUUID: true,
|
||||
filterUUID: true,
|
||||
remainingContent: -1,
|
||||
remainingPadding: -1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *VisionConn) Read(p []byte) (n int, err error) {
|
||||
if c.remainingReader != nil {
|
||||
n, err = c.remainingReader.Read(p)
|
||||
if err == io.EOF {
|
||||
c.remainingReader = nil
|
||||
if n > 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.directRead {
|
||||
return c.netConn.Read(p)
|
||||
}
|
||||
n, err = c.Conn.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buffer := p[:n]
|
||||
if c.filterUUID && (c.isTLS || c.numberOfPacketToFilter > 0) {
|
||||
buffers := c.unPadding(buffer)
|
||||
if c.remainingContent == 0 && c.remainingPadding == 0 {
|
||||
if c.currentCommand == 1 {
|
||||
c.filterUUID = false
|
||||
} else if c.currentCommand == 2 {
|
||||
c.filterUUID = false
|
||||
c.directRead = true
|
||||
|
||||
inputBuffer, err := io.ReadAll(c.input)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
buffers = append(buffers, inputBuffer)
|
||||
|
||||
rawInputBuffer, err := io.ReadAll(c.rawInput)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
buffers = append(buffers, rawInputBuffer)
|
||||
} else if c.currentCommand != 0 {
|
||||
return 0, E.New("unknown command ", c.currentCommand)
|
||||
}
|
||||
}
|
||||
if c.numberOfPacketToFilter > 0 {
|
||||
c.filterTLS(buffers)
|
||||
}
|
||||
c.remainingReader = io.MultiReader(common.Map(buffers, func(it []byte) io.Reader { return bytes.NewReader(it) })...)
|
||||
return c.remainingReader.Read(p)
|
||||
} else {
|
||||
if c.numberOfPacketToFilter > 0 {
|
||||
c.filterTLS([][]byte{buffer})
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *VisionConn) Write(p []byte) (n int, err error) {
|
||||
if c.numberOfPacketToFilter > 0 {
|
||||
c.filterTLS([][]byte{p})
|
||||
}
|
||||
if c.isTLS && c.filterTlsApplicationData {
|
||||
inputLen := len(p)
|
||||
buffers := reshapeBuffer(p)
|
||||
var specIndex int
|
||||
for i, buffer := range buffers {
|
||||
if buffer.Len() > 6 && bytes.Equal(tlsApplicationDataStart, buffer.To(3)) {
|
||||
var command byte = 1
|
||||
if c.enableXTLS {
|
||||
c.directWrite = true
|
||||
specIndex = i
|
||||
command = 2
|
||||
}
|
||||
c.filterTlsApplicationData = false
|
||||
buffers[i] = c.padding(buffer, command)
|
||||
break
|
||||
} else if !c.isTLS12orAbove && c.numberOfPacketToFilter == 0 {
|
||||
c.filterTlsApplicationData = false
|
||||
buffers[i] = c.padding(buffer, 0x01)
|
||||
break
|
||||
}
|
||||
buffers[i] = c.padding(buffer, 0x00)
|
||||
}
|
||||
if c.directWrite {
|
||||
encryptedBuffer := buffers[:specIndex+1]
|
||||
err = c.writer.WriteVectorised(encryptedBuffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buffers = buffers[specIndex+1:]
|
||||
c.writer = bufio.NewVectorisedWriter(c.netConn)
|
||||
time.Sleep(5 * time.Millisecond) // wtf
|
||||
}
|
||||
err = c.writer.WriteVectorised(buffers)
|
||||
if err == nil {
|
||||
n = inputLen
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.directWrite {
|
||||
return c.netConn.Write(p)
|
||||
} else {
|
||||
return c.Conn.Write(p)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *VisionConn) filterTLS(buffers [][]byte) {
|
||||
for _, buffer := range buffers {
|
||||
c.numberOfPacketToFilter--
|
||||
if len(buffer) > 6 {
|
||||
if buffer[0] == 22 && buffer[1] == 3 && buffer[2] == 3 {
|
||||
c.isTLS = true
|
||||
if buffer[5] == 2 {
|
||||
c.isTLS12orAbove = true
|
||||
c.remainingServerHello = (int32(buffer[3])<<8 | int32(buffer[4])) + 5
|
||||
if len(buffer) >= 79 && c.remainingServerHello >= 79 {
|
||||
sessionIdLen := int32(buffer[43])
|
||||
cipherSuite := buffer[43+sessionIdLen+1 : 43+sessionIdLen+3]
|
||||
c.cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1])
|
||||
}
|
||||
}
|
||||
} else if bytes.Equal(tlsClientHandShakeStart, buffer[:2]) && buffer[5] == 1 {
|
||||
c.isTLS = true
|
||||
}
|
||||
}
|
||||
if c.remainingServerHello > 0 {
|
||||
end := int(c.remainingServerHello)
|
||||
if end > len(buffer) {
|
||||
end = len(buffer)
|
||||
}
|
||||
c.remainingServerHello -= int32(end)
|
||||
if bytes.Contains(buffer[:end], tls13SupportedVersions) {
|
||||
cipher, ok := tls13CipherSuiteDic[c.cipher]
|
||||
if ok && cipher != "TLS_AES_128_CCM_8_SHA256" {
|
||||
c.enableXTLS = true
|
||||
}
|
||||
c.numberOfPacketToFilter = 0
|
||||
return
|
||||
} else if c.remainingServerHello == 0 {
|
||||
c.numberOfPacketToFilter = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *VisionConn) padding(buffer *buf.Buffer, command byte) *buf.Buffer {
|
||||
contentLen := 0
|
||||
paddingLen := 0
|
||||
if buffer != nil {
|
||||
contentLen = buffer.Len()
|
||||
}
|
||||
if contentLen < 900 {
|
||||
l, _ := rand.Int(rand.Reader, big.NewInt(500))
|
||||
paddingLen = int(l.Int64()) + 900 - contentLen
|
||||
}
|
||||
newBuffer := buf.New()
|
||||
if c.writeUUID {
|
||||
newBuffer.Write(c.userUUID[:])
|
||||
c.writeUUID = false
|
||||
}
|
||||
newBuffer.Write([]byte{command, byte(contentLen >> 8), byte(contentLen), byte(paddingLen >> 8), byte(paddingLen)})
|
||||
if buffer != nil {
|
||||
newBuffer.Write(buffer.Bytes())
|
||||
buffer.Release()
|
||||
}
|
||||
newBuffer.Extend(paddingLen)
|
||||
return newBuffer
|
||||
}
|
||||
|
||||
func (c *VisionConn) unPadding(buffer []byte) [][]byte {
|
||||
var bufferIndex int
|
||||
if c.remainingContent == -1 && c.remainingPadding == -1 {
|
||||
if len(buffer) >= 21 && bytes.Equal(c.userUUID[:], buffer[:16]) {
|
||||
bufferIndex = 16
|
||||
c.remainingContent = 0
|
||||
c.remainingPadding = 0
|
||||
}
|
||||
}
|
||||
if c.remainingContent == -1 && c.remainingPadding == -1 {
|
||||
return [][]byte{buffer}
|
||||
}
|
||||
var buffers [][]byte
|
||||
for bufferIndex < len(buffer) {
|
||||
if c.remainingContent <= 0 && c.remainingPadding <= 0 {
|
||||
if c.currentCommand == 1 {
|
||||
buffers = append(buffers, buffer[bufferIndex:])
|
||||
break
|
||||
} else {
|
||||
paddingInfo := buffer[bufferIndex : bufferIndex+5]
|
||||
c.currentCommand = int(paddingInfo[0])
|
||||
c.remainingContent = int(paddingInfo[1])<<8 | int(paddingInfo[2])
|
||||
c.remainingPadding = int(paddingInfo[3])<<8 | int(paddingInfo[4])
|
||||
bufferIndex += 5
|
||||
}
|
||||
} else if c.remainingContent > 0 {
|
||||
end := c.remainingContent
|
||||
if end > len(buffer)-bufferIndex {
|
||||
end = len(buffer) - bufferIndex
|
||||
}
|
||||
buffers = append(buffers, buffer[bufferIndex:bufferIndex+end])
|
||||
c.remainingContent -= end
|
||||
bufferIndex += end
|
||||
} else {
|
||||
end := c.remainingPadding
|
||||
if end > len(buffer)-bufferIndex {
|
||||
end = len(buffer) - bufferIndex
|
||||
}
|
||||
c.remainingPadding -= end
|
||||
bufferIndex += end
|
||||
}
|
||||
if bufferIndex == len(buffer) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buffers
|
||||
}
|
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…
Reference in a new issue