mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-24 02:26:48 +08:00
Compare commits
1 Commits
b2829219a0
...
maps
Author | SHA1 | Date | |
---|---|---|---|
![]() |
20825f6f1a |
@@ -1,16 +1,17 @@
|
|||||||
# Project X
|
# Project X
|
||||||
|
|
||||||
|
[](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
||||||
|
|
||||||
|
### [Collect a Project X NFT to support the development of Project X!](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
||||||
|
|
||||||
[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY).
|
[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY).
|
||||||
|
|
||||||
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
|
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
|
||||||
|
|
||||||
## Donation & NFTs
|
## Donation & NFTs
|
||||||
|
|
||||||
### [Collect a Project X NFT to support the development of Project X!](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
|
||||||
|
|
||||||
[<img alt="Project X NFT" width="150px" src="https://raw2.seadn.io/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/7fa9ce900fb39b44226348db330e32/8b7fa9ce900fb39b44226348db330e32.svg" />](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
|
||||||
|
|
||||||
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
|
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
|
||||||
|
- **Project X NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1**
|
||||||
- **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2**
|
- **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2**
|
||||||
- **Related links: https://opensea.io/collection/xtls, [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)**
|
- **Related links: https://opensea.io/collection/xtls, [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)**
|
||||||
|
|
||||||
|
@@ -15,8 +15,8 @@ type TypedSyncMap[K, V any] struct {
|
|||||||
// K is key type, V is value type
|
// K is key type, V is value type
|
||||||
// It is recommended to use pointer types for V because sync.Map might return nil
|
// It is recommended to use pointer types for V because sync.Map might return nil
|
||||||
// If sync.Map methods really returned nil, it will return the zero value of the type V
|
// If sync.Map methods really returned nil, it will return the zero value of the type V
|
||||||
func NewTypedSyncMap[K any, V any]() *TypedSyncMap[K, V] {
|
func NewTypedSyncMap[K any, V any]() TypedSyncMap[K, V] {
|
||||||
return &TypedSyncMap[K, V]{
|
return TypedSyncMap[K, V]{
|
||||||
syncMap: &sync.Map{},
|
syncMap: &sync.Map{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,21 +6,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DokodemoConfig struct {
|
type DokodemoConfig struct {
|
||||||
Address *Address `json:"address"`
|
Host *Address `json:"address"`
|
||||||
Port uint16 `json:"port"`
|
PortValue uint16 `json:"port"`
|
||||||
Network *NetworkList `json:"network"`
|
NetworkList *NetworkList `json:"network"`
|
||||||
FollowRedirect bool `json:"followRedirect"`
|
Redirect bool `json:"followRedirect"`
|
||||||
UserLevel uint32 `json:"userLevel"`
|
UserLevel uint32 `json:"userLevel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *DokodemoConfig) Build() (proto.Message, error) {
|
func (v *DokodemoConfig) Build() (proto.Message, error) {
|
||||||
config := new(dokodemo.Config)
|
config := new(dokodemo.Config)
|
||||||
if v.Address != nil {
|
if v.Host != nil {
|
||||||
config.Address = v.Address.Build()
|
config.Address = v.Host.Build()
|
||||||
}
|
}
|
||||||
config.Port = uint32(v.Port)
|
config.Port = uint32(v.PortValue)
|
||||||
config.Networks = v.Network.Build()
|
config.Networks = v.NetworkList.Build()
|
||||||
config.FollowRedirect = v.FollowRedirect
|
config.FollowRedirect = v.Redirect
|
||||||
config.UserLevel = v.UserLevel
|
config.UserLevel = v.UserLevel
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
@@ -412,9 +412,8 @@ type TLSConfig struct {
|
|||||||
MasterKeyLog string `json:"masterKeyLog"`
|
MasterKeyLog string `json:"masterKeyLog"`
|
||||||
ServerNameToVerify string `json:"serverNameToVerify"`
|
ServerNameToVerify string `json:"serverNameToVerify"`
|
||||||
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
||||||
ECHServerKeys string `json:"echServerKeys"`
|
|
||||||
ECHConfigList string `json:"echConfigList"`
|
ECHConfigList string `json:"echConfigList"`
|
||||||
ECHForceQuery bool `json:"echForceQuery"`
|
ECHServerKeys string `json:"echServerKeys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@@ -486,6 +485,8 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
||||||
|
|
||||||
|
config.EchConfigList = c.ECHConfigList
|
||||||
|
|
||||||
if c.ECHServerKeys != "" {
|
if c.ECHServerKeys != "" {
|
||||||
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.ECHServerKeys)
|
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.ECHServerKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -493,8 +494,6 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
config.EchServerKeys = EchPrivateKey
|
config.EchServerKeys = EchPrivateKey
|
||||||
}
|
}
|
||||||
config.EchForceQuery = c.ECHForceQuery
|
|
||||||
config.EchConfigList = c.ECHConfigList
|
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,6 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
"tunnel": func() interface{} { return new(DokodemoConfig) },
|
|
||||||
"dokodemo-door": func() interface{} { return new(DokodemoConfig) },
|
"dokodemo-door": func() interface{} { return new(DokodemoConfig) },
|
||||||
"http": func() interface{} { return new(HTTPServerConfig) },
|
"http": func() interface{} { return new(HTTPServerConfig) },
|
||||||
"shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) },
|
"shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) },
|
||||||
@@ -34,10 +33,8 @@ var (
|
|||||||
}, "protocol", "settings")
|
}, "protocol", "settings")
|
||||||
|
|
||||||
outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||||
"block": func() interface{} { return new(BlackholeConfig) },
|
|
||||||
"blackhole": func() interface{} { return new(BlackholeConfig) },
|
"blackhole": func() interface{} { return new(BlackholeConfig) },
|
||||||
"loopback": func() interface{} { return new(LoopbackConfig) },
|
"loopback": func() interface{} { return new(LoopbackConfig) },
|
||||||
"direct": func() interface{} { return new(FreedomConfig) },
|
|
||||||
"freedom": func() interface{} { return new(FreedomConfig) },
|
"freedom": func() interface{} { return new(FreedomConfig) },
|
||||||
"http": func() interface{} { return new(HTTPClientConfig) },
|
"http": func() interface{} { return new(HTTPClientConfig) },
|
||||||
"shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) },
|
"shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) },
|
||||||
@@ -245,7 +242,7 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
|||||||
return nil, errors.New("failed to load inbound detour config for protocol ", c.Protocol).Base(err)
|
return nil, errors.New("failed to load inbound detour config for protocol ", c.Protocol).Base(err)
|
||||||
}
|
}
|
||||||
if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok {
|
if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok {
|
||||||
receiverSettings.ReceiveOriginalDestination = dokodemoConfig.FollowRedirect
|
receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect
|
||||||
}
|
}
|
||||||
ts, err := rawConfig.(Buildable).Build()
|
ts, err := rawConfig.(Buildable).Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -3,8 +3,6 @@ package dokodemo
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
@@ -75,25 +73,6 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
|
|||||||
Port: d.port,
|
Port: d.port,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.config.FollowRedirect {
|
|
||||||
host, port, err := net.SplitHostPort(conn.LocalAddr().String())
|
|
||||||
if dest.Address == nil {
|
|
||||||
if err != nil {
|
|
||||||
dest.Address = net.DomainAddress("localhost")
|
|
||||||
} else {
|
|
||||||
if strings.Contains(host, ".") {
|
|
||||||
dest.Address = net.LocalHostIP
|
|
||||||
} else {
|
|
||||||
dest.Address = net.LocalHostIPv6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dest.Port == 0 {
|
|
||||||
dest.Port = net.Port(common.Must2(strconv.Atoi(port)).(int))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
destinationOverridden := false
|
destinationOverridden := false
|
||||||
if d.config.FollowRedirect {
|
if d.config.FollowRedirect {
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
@@ -363,7 +363,7 @@ func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride
|
|||||||
Handler: h,
|
Handler: h,
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
UDPOverride: UDPOverride,
|
UDPOverride: UDPOverride,
|
||||||
resolvedUDPAddr: resolvedUDPAddr,
|
resolvedUDPAddr: &resolvedUDPAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,17 +2,17 @@ package trojan
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validator stores valid trojan users.
|
// Validator stores valid trojan users.
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
|
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
|
||||||
email sync.Map
|
email utils.TypedSyncMap[string, *protocol.MemoryUser]
|
||||||
users sync.Map
|
users utils.TypedSyncMap[string, *protocol.MemoryUser]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a trojan user, Email must be empty or unique.
|
// Add a trojan user, Email must be empty or unique.
|
||||||
@@ -38,7 +38,7 @@ func (v *Validator) Del(e string) error {
|
|||||||
return errors.New("User ", e, " not found.")
|
return errors.New("User ", e, " not found.")
|
||||||
}
|
}
|
||||||
v.email.Delete(le)
|
v.email.Delete(le)
|
||||||
v.users.Delete(hexString(u.(*protocol.MemoryUser).Account.(*MemoryAccount).Key))
|
v.users.Delete(hexString(u.Account.(*MemoryAccount).Key))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ func (v *Validator) Del(e string) error {
|
|||||||
func (v *Validator) Get(hash string) *protocol.MemoryUser {
|
func (v *Validator) Get(hash string) *protocol.MemoryUser {
|
||||||
u, _ := v.users.Load(hash)
|
u, _ := v.users.Load(hash)
|
||||||
if u != nil {
|
if u != nil {
|
||||||
return u.(*protocol.MemoryUser)
|
return u
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func (v *Validator) GetByEmail(email string) *protocol.MemoryUser {
|
|||||||
email = strings.ToLower(email)
|
email = strings.ToLower(email)
|
||||||
u, _ := v.email.Load(email)
|
u, _ := v.email.Load(email)
|
||||||
if u != nil {
|
if u != nil {
|
||||||
return u.(*protocol.MemoryUser)
|
return u
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -64,8 +64,8 @@ func (v *Validator) GetByEmail(email string) *protocol.MemoryUser {
|
|||||||
// Get all users
|
// Get all users
|
||||||
func (v *Validator) GetAll() []*protocol.MemoryUser {
|
func (v *Validator) GetAll() []*protocol.MemoryUser {
|
||||||
var u = make([]*protocol.MemoryUser, 0, 100)
|
var u = make([]*protocol.MemoryUser, 0, 100)
|
||||||
v.email.Range(func(key, value interface{}) bool {
|
v.email.Range(func(key string, value *protocol.MemoryUser) bool {
|
||||||
u = append(u, value.(*protocol.MemoryUser))
|
u = append(u, value)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return u
|
return u
|
||||||
@@ -74,7 +74,7 @@ func (v *Validator) GetAll() []*protocol.MemoryUser {
|
|||||||
// Get users count
|
// Get users count
|
||||||
func (v *Validator) GetCount() int64 {
|
func (v *Validator) GetCount() int64 {
|
||||||
var c int64 = 0
|
var c int64 = 0
|
||||||
v.email.Range(func(key, value interface{}) bool {
|
v.email.Range(func(key string, value *protocol.MemoryUser) bool {
|
||||||
c++
|
c++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@@ -2,10 +2,10 @@ package vless
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
"github.com/xtls/xray-core/common/uuid"
|
"github.com/xtls/xray-core/common/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,8 +21,8 @@ type Validator interface {
|
|||||||
// MemoryValidator stores valid VLESS users.
|
// MemoryValidator stores valid VLESS users.
|
||||||
type MemoryValidator struct {
|
type MemoryValidator struct {
|
||||||
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
|
// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
|
||||||
email sync.Map
|
email utils.TypedSyncMap[string, *protocol.MemoryUser]
|
||||||
users sync.Map
|
users utils.TypedSyncMap[uuid.UUID, *protocol.MemoryUser]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a VLESS user, Email must be empty or unique.
|
// Add a VLESS user, Email must be empty or unique.
|
||||||
@@ -48,7 +48,7 @@ func (v *MemoryValidator) Del(e string) error {
|
|||||||
return errors.New("User ", e, " not found.")
|
return errors.New("User ", e, " not found.")
|
||||||
}
|
}
|
||||||
v.email.Delete(le)
|
v.email.Delete(le)
|
||||||
v.users.Delete(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.UUID())
|
v.users.Delete(u.Account.(*MemoryAccount).ID.UUID())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ func (v *MemoryValidator) Del(e string) error {
|
|||||||
func (v *MemoryValidator) Get(id uuid.UUID) *protocol.MemoryUser {
|
func (v *MemoryValidator) Get(id uuid.UUID) *protocol.MemoryUser {
|
||||||
u, _ := v.users.Load(id)
|
u, _ := v.users.Load(id)
|
||||||
if u != nil {
|
if u != nil {
|
||||||
return u.(*protocol.MemoryUser)
|
return u
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func (v *MemoryValidator) GetByEmail(email string) *protocol.MemoryUser {
|
|||||||
email = strings.ToLower(email)
|
email = strings.ToLower(email)
|
||||||
u, _ := v.email.Load(email)
|
u, _ := v.email.Load(email)
|
||||||
if u != nil {
|
if u != nil {
|
||||||
return u.(*protocol.MemoryUser)
|
return u
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -74,8 +74,8 @@ func (v *MemoryValidator) GetByEmail(email string) *protocol.MemoryUser {
|
|||||||
// Get all users
|
// Get all users
|
||||||
func (v *MemoryValidator) GetAll() []*protocol.MemoryUser {
|
func (v *MemoryValidator) GetAll() []*protocol.MemoryUser {
|
||||||
var u = make([]*protocol.MemoryUser, 0, 100)
|
var u = make([]*protocol.MemoryUser, 0, 100)
|
||||||
v.email.Range(func(key, value interface{}) bool {
|
v.email.Range(func(key string, value *protocol.MemoryUser) bool {
|
||||||
u = append(u, value.(*protocol.MemoryUser))
|
u = append(u, value)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return u
|
return u
|
||||||
@@ -84,7 +84,7 @@ func (v *MemoryValidator) GetAll() []*protocol.MemoryUser {
|
|||||||
// Get users count
|
// Get users count
|
||||||
func (v *MemoryValidator) GetCount() int64 {
|
func (v *MemoryValidator) GetCount() int64 {
|
||||||
var c int64 = 0
|
var c int64 = 0
|
||||||
v.email.Range(func(key, value interface{}) bool {
|
v.email.Range(func(key string, value *protocol.MemoryUser) bool {
|
||||||
c++
|
c++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@@ -43,7 +43,7 @@ type dialerConf struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
globalDialerMap map[dialerConf]*grpc.ClientConn
|
globalDialerMap = make(map[dialerConf]*grpc.ClientConn)
|
||||||
globalDialerAccess sync.Mutex
|
globalDialerAccess sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,9 +77,6 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
|
|||||||
globalDialerAccess.Lock()
|
globalDialerAccess.Lock()
|
||||||
defer globalDialerAccess.Unlock()
|
defer globalDialerAccess.Unlock()
|
||||||
|
|
||||||
if globalDialerMap == nil {
|
|
||||||
globalDialerMap = make(map[dialerConf]*grpc.ClientConn)
|
|
||||||
}
|
|
||||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||||
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
realityConfig := reality.ConfigFromStreamSettings(streamSettings)
|
||||||
sockopt := streamSettings.SocketSettings
|
sockopt := streamSettings.SocketSettings
|
||||||
|
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
http_proto "github.com/xtls/xray-core/common/protocol/http"
|
http_proto "github.com/xtls/xray-core/common/protocol/http"
|
||||||
"github.com/xtls/xray-core/common/signal/done"
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"github.com/xtls/xray-core/transport/internet/reality"
|
"github.com/xtls/xray-core/transport/internet/reality"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
@@ -32,7 +33,7 @@ type requestHandler struct {
|
|||||||
path string
|
path string
|
||||||
ln *Listener
|
ln *Listener
|
||||||
sessionMu *sync.Mutex
|
sessionMu *sync.Mutex
|
||||||
sessions sync.Map
|
sessions utils.TypedSyncMap[string, *httpSession]
|
||||||
localAddr net.Addr
|
localAddr net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,18 +48,18 @@ type httpSession struct {
|
|||||||
|
|
||||||
func (h *requestHandler) upsertSession(sessionId string) *httpSession {
|
func (h *requestHandler) upsertSession(sessionId string) *httpSession {
|
||||||
// fast path
|
// fast path
|
||||||
currentSessionAny, ok := h.sessions.Load(sessionId)
|
currentSession, ok := h.sessions.Load(sessionId)
|
||||||
if ok {
|
if ok {
|
||||||
return currentSessionAny.(*httpSession)
|
return currentSession
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow path
|
// slow path
|
||||||
h.sessionMu.Lock()
|
h.sessionMu.Lock()
|
||||||
defer h.sessionMu.Unlock()
|
defer h.sessionMu.Unlock()
|
||||||
|
|
||||||
currentSessionAny, ok = h.sessions.Load(sessionId)
|
currentSession, ok = h.sessions.Load(sessionId)
|
||||||
if ok {
|
if ok {
|
||||||
return currentSessionAny.(*httpSession)
|
return currentSession
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &httpSession{
|
s := &httpSession{
|
||||||
@@ -361,7 +362,7 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
|
|||||||
path: l.config.GetNormalizedPath(),
|
path: l.config.GetNormalizedPath(),
|
||||||
ln: l,
|
ln: l,
|
||||||
sessionMu: &sync.Mutex{},
|
sessionMu: &sync.Mutex{},
|
||||||
sessions: sync.Map{},
|
sessions: utils.NewTypedSyncMap[string, *httpSession](),
|
||||||
}
|
}
|
||||||
tlsConfig := getTLSConfig(streamSettings)
|
tlsConfig := getTLSConfig(streamSettings)
|
||||||
l.isH3 = len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "h3"
|
l.isH3 = len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == "h3"
|
||||||
|
@@ -217,9 +217,8 @@ type Config struct {
|
|||||||
// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
|
// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
|
||||||
// @Critical
|
// @Critical
|
||||||
VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
|
VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
|
||||||
EchServerKeys []byte `protobuf:"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"`
|
EchConfigList string `protobuf:"bytes,18,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
|
||||||
EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
|
EchServerKeys []byte `protobuf:"bytes,19,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"`
|
||||||
EchForceQuery bool `protobuf:"varint,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
@@ -364,13 +363,6 @@ func (x *Config) GetVerifyPeerCertInNames() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetEchServerKeys() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.EchServerKeys
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Config) GetEchConfigList() string {
|
func (x *Config) GetEchConfigList() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.EchConfigList
|
return x.EchConfigList
|
||||||
@@ -378,11 +370,11 @@ func (x *Config) GetEchConfigList() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetEchForceQuery() bool {
|
func (x *Config) GetEchServerKeys() []byte {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.EchForceQuery
|
return x.EchServerKeys
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
||||||
@@ -416,7 +408,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
|||||||
0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
|
0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
|
||||||
0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46,
|
0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46,
|
||||||
0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59,
|
0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59,
|
||||||
0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x92, 0x07, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xea, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
||||||
0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
|
0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
|
||||||
0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c,
|
0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c,
|
||||||
0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
||||||
@@ -466,22 +458,20 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
|||||||
0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
|
0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
|
||||||
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
|
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
|
||||||
0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
||||||
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x73, 0x65, 0x72,
|
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e,
|
||||||
0x76, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d,
|
0x66, 0x69, 0x67, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
|
||||||
0x65, 0x63, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x26, 0x0a,
|
0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a,
|
||||||
0x0f, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6c, 0x69, 0x73, 0x74,
|
0x0f, 0x65, 0x63, 0x68, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x73,
|
||||||
0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x18, 0x13, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x63, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||||
0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x66, 0x6f, 0x72,
|
0x72, 0x4b, 0x65, 0x79, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
|
||||||
0x63, 0x65, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d,
|
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
||||||
0x65, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x42, 0x73, 0x0a,
|
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
|
||||||
0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
|
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
|
||||||
0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73,
|
0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f,
|
||||||
0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
|
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58,
|
||||||
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72,
|
0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e,
|
||||||
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
|
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e,
|
0x6f, 0x33,
|
||||||
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54,
|
|
||||||
0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -92,9 +92,7 @@ message Config {
|
|||||||
*/
|
*/
|
||||||
repeated string verify_peer_cert_in_names = 17;
|
repeated string verify_peer_cert_in_names = 17;
|
||||||
|
|
||||||
bytes ech_server_keys = 18;
|
string ech_config_list = 18;
|
||||||
|
|
||||||
string ech_config_list = 19;
|
bytes ech_server_keys = 19;
|
||||||
|
|
||||||
bool ech_force_query = 20;
|
|
||||||
}
|
}
|
@@ -32,26 +32,8 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
|||||||
nameToQuery := c.ServerName
|
nameToQuery := c.ServerName
|
||||||
var DNSServer string
|
var DNSServer string
|
||||||
|
|
||||||
// for server
|
|
||||||
if len(c.EchServerKeys) != 0 {
|
|
||||||
KeySets, err := ConvertToGoECHKeys(c.EchServerKeys)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("Failed to unmarshal ECHKeySetList: ", err)
|
|
||||||
}
|
|
||||||
config.EncryptedClientHelloKeys = KeySets
|
|
||||||
}
|
|
||||||
|
|
||||||
// for client
|
// for client
|
||||||
if len(c.EchConfigList) != 0 {
|
if len(c.EchConfigList) != 0 {
|
||||||
defer func() {
|
|
||||||
// if failed to get ECHConfig, use an invalid one to make connection fail
|
|
||||||
if err != nil {
|
|
||||||
if c.EchForceQuery {
|
|
||||||
ECHConfig = []byte{1, 1, 4, 5, 1, 4}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config.EncryptedClientHelloConfigList = ECHConfig
|
|
||||||
}()
|
|
||||||
// direct base64 config
|
// direct base64 config
|
||||||
if strings.Contains(c.EchConfigList, "://") {
|
if strings.Contains(c.EchConfigList, "://") {
|
||||||
// query config from dns
|
// query config from dns
|
||||||
@@ -69,7 +51,7 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
|||||||
if nameToQuery == "" {
|
if nameToQuery == "" {
|
||||||
return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query")
|
return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query")
|
||||||
}
|
}
|
||||||
ECHConfig, err = QueryRecord(nameToQuery, DNSServer, c.EchForceQuery)
|
ECHConfig, err = QueryRecord(nameToQuery, DNSServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -79,6 +61,17 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
|||||||
return errors.New("Failed to unmarshal ECHConfigList: ", err)
|
return errors.New("Failed to unmarshal ECHConfigList: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.EncryptedClientHelloConfigList = ECHConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// for server
|
||||||
|
if len(c.EchServerKeys) != 0 {
|
||||||
|
KeySets, err := ConvertToGoECHKeys(c.EchServerKeys)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to unmarshal ECHKeySetList: ", err)
|
||||||
|
}
|
||||||
|
config.EncryptedClientHelloKeys = KeySets
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -93,11 +86,9 @@ type ECHConfigCache struct {
|
|||||||
type echConfigRecord struct {
|
type echConfigRecord struct {
|
||||||
config []byte
|
config []byte
|
||||||
expire time.Time
|
expire time.Time
|
||||||
err error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// key value must be like this: "example.com|udp://1.1.1.1"
|
|
||||||
GlobalECHConfigCache = utils.NewTypedSyncMap[string, *ECHConfigCache]()
|
GlobalECHConfigCache = utils.NewTypedSyncMap[string, *ECHConfigCache]()
|
||||||
clientForECHDOH = utils.NewTypedSyncMap[string, *http.Client]()
|
clientForECHDOH = utils.NewTypedSyncMap[string, *http.Client]()
|
||||||
)
|
)
|
||||||
@@ -105,7 +96,7 @@ var (
|
|||||||
// Update updates the ECH config for given domain and server.
|
// Update updates the ECH config for given domain and server.
|
||||||
// this method is concurrent safe, only one update request will be sent, others get the cache.
|
// this method is concurrent safe, only one update request will be sent, others get the cache.
|
||||||
// if isLockedUpdate is true, it will not try to acquire the lock.
|
// if isLockedUpdate is true, it will not try to acquire the lock.
|
||||||
func (c *ECHConfigCache) Update(domain string, server string, forceQuery bool, isLockedUpdate bool) ([]byte, error) {
|
func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool) ([]byte, error) {
|
||||||
if !isLockedUpdate {
|
if !isLockedUpdate {
|
||||||
c.UpdateLock.Lock()
|
c.UpdateLock.Lock()
|
||||||
defer c.UpdateLock.Unlock()
|
defer c.UpdateLock.Unlock()
|
||||||
@@ -114,23 +105,13 @@ func (c *ECHConfigCache) Update(domain string, server string, forceQuery bool, i
|
|||||||
configRecord := c.configRecord.Load()
|
configRecord := c.configRecord.Load()
|
||||||
if configRecord.expire.After(time.Now()) {
|
if configRecord.expire.After(time.Now()) {
|
||||||
errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain)
|
errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain)
|
||||||
return configRecord.config, configRecord.err
|
return configRecord.config, nil
|
||||||
}
|
}
|
||||||
// Query ECH config from DNS server
|
// Query ECH config from DNS server
|
||||||
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
|
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
|
||||||
echConfig, ttl, err := dnsQuery(server, domain)
|
echConfig, ttl, err := dnsQuery(server, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if forceQuery {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
configRecord = &echConfigRecord{
|
|
||||||
config: nil,
|
|
||||||
expire: time.Now().Add(10 * time.Minute),
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
c.configRecord.Store(configRecord)
|
|
||||||
return echConfig, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
configRecord = &echConfigRecord{
|
configRecord = &echConfigRecord{
|
||||||
config: echConfig,
|
config: echConfig,
|
||||||
@@ -142,31 +123,30 @@ func (c *ECHConfigCache) Update(domain string, server string, forceQuery bool, i
|
|||||||
|
|
||||||
// QueryRecord returns the ECH config for given domain.
|
// QueryRecord returns the ECH config for given domain.
|
||||||
// If the record is not in cache or expired, it will query the DNS server and update the cache.
|
// If the record is not in cache or expired, it will query the DNS server and update the cache.
|
||||||
func QueryRecord(domain string, server string, forceQuery bool) ([]byte, error) {
|
func QueryRecord(domain string, server string) ([]byte, error) {
|
||||||
GlobalECHConfigCacheKey := domain + "|" + server
|
echConfigCache, ok := GlobalECHConfigCache.Load(domain)
|
||||||
echConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
echConfigCache = &ECHConfigCache{}
|
echConfigCache = &ECHConfigCache{}
|
||||||
echConfigCache.configRecord.Store(&echConfigRecord{})
|
echConfigCache.configRecord.Store(&echConfigRecord{})
|
||||||
echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache)
|
echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(domain, echConfigCache)
|
||||||
}
|
}
|
||||||
configRecord := echConfigCache.configRecord.Load()
|
configRecord := echConfigCache.configRecord.Load()
|
||||||
if configRecord.expire.After(time.Now()) {
|
if configRecord.expire.After(time.Now()) {
|
||||||
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
|
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
|
||||||
return configRecord.config, configRecord.err
|
return configRecord.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
||||||
// otherwise return old value immediately and update in a goroutine
|
// otherwise return old value immediately and update in a goroutine
|
||||||
// but if the cache is too old, wait for update
|
// but if the cache is too old, wait for update
|
||||||
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*6).Before(time.Now()) {
|
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*6).Before(time.Now()) {
|
||||||
return echConfigCache.Update(domain, server, false, forceQuery)
|
return echConfigCache.Update(domain, server, false)
|
||||||
} else {
|
} else {
|
||||||
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||||
if echConfigCache.UpdateLock.TryLock() {
|
if echConfigCache.UpdateLock.TryLock() {
|
||||||
go func() {
|
go func() {
|
||||||
defer echConfigCache.UpdateLock.Unlock()
|
defer echConfigCache.UpdateLock.Unlock()
|
||||||
echConfigCache.Update(domain, server, true, forceQuery)
|
echConfigCache.Update(domain, server, true)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return configRecord.config, nil
|
return configRecord.config, nil
|
||||||
@@ -185,7 +165,7 @@ func dnsQuery(server string, domain string) ([]byte, uint32, error) {
|
|||||||
m.Id = 0
|
m.Id = 0
|
||||||
msg, err := m.Pack()
|
msg, err := m.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return []byte{}, 0, err
|
||||||
}
|
}
|
||||||
var client *http.Client
|
var client *http.Client
|
||||||
if client, _ = clientForECHDOH.Load(server); client == nil {
|
if client, _ = clientForECHDOH.Load(server); client == nil {
|
||||||
@@ -214,20 +194,20 @@ func dnsQuery(server string, domain string) ([]byte, uint32, error) {
|
|||||||
}
|
}
|
||||||
req, err := http.NewRequest("POST", server, bytes.NewReader(msg))
|
req, err := http.NewRequest("POST", server, bytes.NewReader(msg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return []byte{}, 0, err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/dns-message")
|
req.Header.Set("Content-Type", "application/dns-message")
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return []byte{}, 0, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
respBody, err := io.ReadAll(resp.Body)
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return []byte{}, 0, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, 0, errors.New("query failed with response code:", resp.StatusCode)
|
return []byte{}, 0, errors.New("query failed with response code:", resp.StatusCode)
|
||||||
}
|
}
|
||||||
dnsResolve = respBody
|
dnsResolve = respBody
|
||||||
} else if strings.HasPrefix(server, "udp://") { // for classic udp dns server
|
} else if strings.HasPrefix(server, "udp://") { // for classic udp dns server
|
||||||
@@ -251,25 +231,24 @@ func dnsQuery(server string, domain string) ([]byte, uint32, error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return []byte{}, 0, err
|
||||||
}
|
}
|
||||||
msg, err := m.Pack()
|
msg, err := m.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return []byte{}, 0, err
|
||||||
}
|
}
|
||||||
conn.Write(msg)
|
conn.Write(msg)
|
||||||
udpResponse := make([]byte, 512)
|
udpResponse := make([]byte, 512)
|
||||||
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
_, err = conn.Read(udpResponse)
|
_, err = conn.Read(udpResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return []byte{}, 0, err
|
||||||
}
|
}
|
||||||
dnsResolve = udpResponse
|
dnsResolve = udpResponse
|
||||||
}
|
}
|
||||||
respMsg := new(dns.Msg)
|
respMsg := new(dns.Msg)
|
||||||
err := respMsg.Unpack(dnsResolve)
|
err := respMsg.Unpack(dnsResolve)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, errors.New("failed to unpack dns response for ECH: ", err)
|
return []byte{}, 0, errors.New("failed to unpack dns response for ECH: ", err)
|
||||||
}
|
}
|
||||||
if len(respMsg.Answer) > 0 {
|
if len(respMsg.Answer) > 0 {
|
||||||
for _, answer := range respMsg.Answer {
|
for _, answer := range respMsg.Answer {
|
||||||
@@ -283,7 +262,7 @@ func dnsQuery(server string, domain string) ([]byte, uint32, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, 0, errors.New("no ech record found")
|
return []byte{}, 0, errors.New("no ech record found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// reference github.com/OmarTariq612/goech
|
// reference github.com/OmarTariq612/goech
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package tls
|
package tls_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@@ -8,12 +8,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
. "github.com/xtls/xray-core/transport/internet/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestECHDial(t *testing.T) {
|
func TestECHDial(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
ServerName: "cloudflare.com",
|
ServerName: "encryptedsni.com",
|
||||||
EchConfigList: "encryptedsni.com+udp://1.1.1.1",
|
EchConfigList: "udp://1.1.1.1",
|
||||||
}
|
}
|
||||||
// test concurrent Dial(to test cache problem)
|
// test concurrent Dial(to test cache problem)
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
@@ -27,7 +28,7 @@ func TestECHDial(t *testing.T) {
|
|||||||
TLSClientConfig: TLSConfig,
|
TLSClientConfig: TLSConfig,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
resp, err := client.Get("https://cloudflare.com/cdn-cgi/trace")
|
resp, err := client.Get("https://encryptedsni.com/cdn-cgi/trace")
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
@@ -39,51 +40,4 @@ func TestECHDial(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
// check cache
|
|
||||||
echConfigCache, ok := GlobalECHConfigCache.Load("encryptedsni.com|udp://1.1.1.1")
|
|
||||||
if !ok {
|
|
||||||
t.Error("ECH config cache not found")
|
|
||||||
|
|
||||||
}
|
|
||||||
ok = echConfigCache.UpdateLock.TryLock()
|
|
||||||
if !ok {
|
|
||||||
t.Error("ECH config cache dead lock detected")
|
|
||||||
}
|
|
||||||
echConfigCache.UpdateLock.Unlock()
|
|
||||||
configRecord := echConfigCache.configRecord.Load()
|
|
||||||
if configRecord == nil {
|
|
||||||
t.Error("ECH config record not found in cache")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestECHDialFail(t *testing.T) {
|
|
||||||
config := &Config{
|
|
||||||
ServerName: "cloudflare.com",
|
|
||||||
EchConfigList: "udp://1.1.1.1",
|
|
||||||
}
|
|
||||||
TLSConfig := config.GetTLSConfig()
|
|
||||||
TLSConfig.NextProtos = []string{"http/1.1"}
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: TLSConfig,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
resp, err := client.Get("https://cloudflare.com/cdn-cgi/trace")
|
|
||||||
common.Must(err)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
_, err = io.ReadAll(resp.Body)
|
|
||||||
common.Must(err)
|
|
||||||
// check cache
|
|
||||||
echConfigCache, ok := GlobalECHConfigCache.Load("cloudflare.com|udp://1.1.1.1")
|
|
||||||
if !ok {
|
|
||||||
t.Error("ECH config cache not found")
|
|
||||||
}
|
|
||||||
configRecord := echConfigCache.configRecord.Load()
|
|
||||||
if configRecord == nil {
|
|
||||||
t.Error("ECH config record not found in cache")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if configRecord.err == nil {
|
|
||||||
t.Error("unexpected nil error in ECH config record")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user