mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-23 23:27:59 +03:00
Compare commits
7
Commits
v26.3.23
...
ech-origin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e819273b7 | ||
|
|
08301e272c | ||
|
|
6eccc59728 | ||
|
|
1e89a8fd98 | ||
|
|
d2758a023c | ||
|
|
8aacdbd71b | ||
|
|
14524cc3b7 |
@@ -36,7 +36,7 @@ type serverityLogger struct {
|
||||
func NewLogger(logWriterCreator WriterCreator) Handler {
|
||||
return &generalLogger{
|
||||
creator: logWriterCreator,
|
||||
buffer: make(chan Message, 16),
|
||||
buffer: make(chan Message, 128),
|
||||
access: semaphore.New(1),
|
||||
done: done.New(),
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func ReplaceWithSeverityLogger(serverity Severity) {
|
||||
w := CreateStdoutLogWriter()
|
||||
g := &generalLogger{
|
||||
creator: w,
|
||||
buffer: make(chan Message, 16),
|
||||
buffer: make(chan Message, 128),
|
||||
access: semaphore.New(1),
|
||||
done: done.New(),
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 26
|
||||
Version_y byte = 3
|
||||
Version_z byte = 23
|
||||
Version_z byte = 27
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -27,7 +27,7 @@ require (
|
||||
golang.org/x/sys v0.42.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
google.golang.org/grpc v1.79.3
|
||||
google.golang.org/grpc v1.80.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||
h12.io/socks v1.0.3
|
||||
@@ -48,7 +48,7 @@ require (
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -131,12 +131,12 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -1425,10 +1425,11 @@ func (c *FragmentMask) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type NoiseItem struct {
|
||||
Rand Int32Range `json:"rand"`
|
||||
Type string `json:"type"`
|
||||
Packet json.RawMessage `json:"packet"`
|
||||
Delay Int32Range `json:"delay"`
|
||||
Rand Int32Range `json:"rand"`
|
||||
RandRange *Int32Range `json:"randRange"`
|
||||
Type string `json:"type"`
|
||||
Packet json.RawMessage `json:"packet"`
|
||||
Delay Int32Range `json:"delay"`
|
||||
}
|
||||
|
||||
type NoiseMask struct {
|
||||
@@ -1445,16 +1446,24 @@ func (c *NoiseMask) Build() (proto.Message, error) {
|
||||
|
||||
noiseSlice := make([]*noise.Item, 0, len(c.Noise))
|
||||
for _, item := range c.Noise {
|
||||
if item.RandRange == nil {
|
||||
item.RandRange = &Int32Range{From: 0, To: 255}
|
||||
}
|
||||
if item.RandRange.From < 0 || item.RandRange.To > 255 {
|
||||
return nil, errors.New("invalid randRange")
|
||||
}
|
||||
var err error
|
||||
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
noiseSlice = append(noiseSlice, &noise.Item{
|
||||
RandMin: int64(item.Rand.From),
|
||||
RandMax: int64(item.Rand.To),
|
||||
Packet: item.Packet,
|
||||
DelayMin: int64(item.Delay.From),
|
||||
DelayMax: int64(item.Delay.To),
|
||||
RandMin: int64(item.Rand.From),
|
||||
RandMax: int64(item.Rand.To),
|
||||
RandRangeMin: item.RandRange.From,
|
||||
RandRangeMax: item.RandRange.To,
|
||||
Packet: item.Packet,
|
||||
DelayMin: int64(item.Delay.From),
|
||||
DelayMax: int64(item.Delay.To),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -83,10 +83,12 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
||||
inbound.Name = "hysteria"
|
||||
inbound.CanSpliceCopy = 3
|
||||
|
||||
iConn := stat.TryUnwrapStatsConn(conn)
|
||||
|
||||
var useremail string
|
||||
var userlevel uint32
|
||||
type User interface{ User() *protocol.MemoryUser }
|
||||
if v, ok := conn.(User); ok {
|
||||
if v, ok := iConn.(User); ok {
|
||||
inbound.User = v.User()
|
||||
if inbound.User != nil {
|
||||
useremail = inbound.User.Email
|
||||
@@ -94,7 +96,6 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
||||
}
|
||||
}
|
||||
|
||||
iConn := stat.TryUnwrapStatsConn(conn)
|
||||
if _, ok := iConn.(*hysteria.InterUdpConn); ok {
|
||||
r := io.Reader(conn)
|
||||
b := make([]byte, MaxUDPSize)
|
||||
|
||||
+50
-50
@@ -2,27 +2,24 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
gonet "net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
type netReadInfo struct {
|
||||
// status
|
||||
waiter sync.WaitGroup
|
||||
// param
|
||||
buff []byte
|
||||
// result
|
||||
bytes int
|
||||
buff *buf.Buffer
|
||||
endpoint conn.Endpoint
|
||||
err error
|
||||
}
|
||||
|
||||
// reduce duplicated code
|
||||
@@ -32,6 +29,7 @@ type netBind struct {
|
||||
|
||||
workers int
|
||||
readQueue chan *netReadInfo
|
||||
closedCh chan struct{}
|
||||
}
|
||||
|
||||
// SetMark implements conn.Bind
|
||||
@@ -79,27 +77,24 @@ func (bind *netBind) BatchSize() int {
|
||||
|
||||
// Open implements conn.Bind
|
||||
func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
|
||||
bind.readQueue = make(chan *netReadInfo)
|
||||
bind.closedCh = make(chan struct{})
|
||||
errors.LogDebug(context.Background(), "bind opened")
|
||||
|
||||
fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
n = 0
|
||||
err = errors.New("channel closed")
|
||||
}
|
||||
}()
|
||||
|
||||
r, ok := <-bind.readQueue
|
||||
if !ok {
|
||||
return 0, errors.New("channel closed")
|
||||
select {
|
||||
case r := <-bind.readQueue:
|
||||
sizes[0], eps[0] = copy(bufs[0], r.buff.Bytes()), r.endpoint
|
||||
r.buff.Release()
|
||||
return 1, nil
|
||||
case <-bind.closedCh:
|
||||
errors.LogDebug(context.Background(), "recv func closed")
|
||||
return 0, gonet.ErrClosed
|
||||
}
|
||||
|
||||
copy(bufs[0], r.buff[:r.bytes])
|
||||
sizes[0], eps[0] = r.bytes, r.endpoint
|
||||
r.waiter.Done()
|
||||
return 1, r.err
|
||||
}
|
||||
workers := bind.workers
|
||||
if workers <= 0 {
|
||||
workers = runtime.NumCPU()
|
||||
}
|
||||
if workers <= 0 {
|
||||
workers = 1
|
||||
}
|
||||
@@ -113,8 +108,9 @@ func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
|
||||
|
||||
// Close implements conn.Bind
|
||||
func (bind *netBind) Close() error {
|
||||
if bind.readQueue != nil {
|
||||
close(bind.readQueue)
|
||||
errors.LogDebug(context.Background(), "bind closed")
|
||||
if bind.closedCh != nil {
|
||||
close(bind.closedCh)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -134,35 +130,38 @@ func (bind *netBindClient) connectTo(endpoint *netEndpoint) error {
|
||||
}
|
||||
endpoint.conn = c
|
||||
|
||||
go func(readQueue chan<- *netReadInfo, endpoint *netEndpoint) {
|
||||
defer func() {
|
||||
_ = recover() // handle send on closed channel
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
buff := make([]byte, 1700)
|
||||
i, err := c.Read(buff)
|
||||
buff := buf.NewWithSize(device.MaxMessageSize)
|
||||
n, err := buff.ReadFrom(c)
|
||||
|
||||
if i > 3 {
|
||||
buff[1] = 0
|
||||
buff[2] = 0
|
||||
buff[3] = 0
|
||||
}
|
||||
|
||||
r := &netReadInfo{
|
||||
buff: buff,
|
||||
bytes: i,
|
||||
endpoint: endpoint,
|
||||
err: err,
|
||||
}
|
||||
r.waiter.Add(1)
|
||||
readQueue <- r
|
||||
r.waiter.Wait()
|
||||
if err != nil {
|
||||
buff.Release()
|
||||
endpoint.conn = nil
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
rawBytes := buff.Bytes()
|
||||
if n > 3 {
|
||||
rawBytes[1] = 0
|
||||
rawBytes[2] = 0
|
||||
rawBytes[3] = 0
|
||||
}
|
||||
|
||||
select {
|
||||
case bind.readQueue <- &netReadInfo{
|
||||
buff: buff,
|
||||
endpoint: endpoint,
|
||||
}:
|
||||
case <-bind.closedCh:
|
||||
buff.Release()
|
||||
endpoint.conn = nil
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}(bind.readQueue, endpoint)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -206,7 +205,8 @@ func (bind *netBindServer) Send(buff [][]byte, endpoint conn.Endpoint) error {
|
||||
}
|
||||
|
||||
if nend.conn == nil {
|
||||
return errors.New("connection not open yet")
|
||||
errors.LogDebug(context.Background(), nend.dst.NetAddr(), " send on closed peer")
|
||||
return errors.New("peer closed")
|
||||
}
|
||||
|
||||
for _, buff := range buff {
|
||||
|
||||
@@ -121,7 +121,8 @@ func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer)
|
||||
IPv4Enable: h.hasIPv4,
|
||||
IPv6Enable: h.hasIPv6,
|
||||
},
|
||||
workers: int(h.conf.NumWorkers),
|
||||
workers: int(h.conf.NumWorkers),
|
||||
readQueue: make(chan *netReadInfo),
|
||||
},
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
|
||||
+24
-16
@@ -2,8 +2,6 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"io"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
c "github.com/xtls/xray-core/common/ctx"
|
||||
@@ -51,6 +49,8 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
|
||||
IPv4Enable: hasIPv4,
|
||||
IPv6Enable: hasIPv6,
|
||||
},
|
||||
workers: int(conf.NumWorkers),
|
||||
readQueue: make(chan *netReadInfo),
|
||||
},
|
||||
},
|
||||
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
|
||||
@@ -93,25 +93,31 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
||||
|
||||
reader := buf.NewPacketReader(conn)
|
||||
for {
|
||||
mpayload, err := reader.ReadMultiBuffer()
|
||||
mb, err := reader.ReadMultiBuffer()
|
||||
if err != nil {
|
||||
nep.conn = nil
|
||||
buf.ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, payload := range mpayload {
|
||||
v, ok := <-s.bindServer.readQueue
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
i, err := payload.Read(v.buff)
|
||||
for i, b := range mb {
|
||||
|
||||
v.bytes = i
|
||||
v.endpoint = nep
|
||||
v.err = err
|
||||
v.waiter.Done()
|
||||
if err != nil && goerrors.Is(err, io.EOF) {
|
||||
rawBytes := b.Bytes()
|
||||
if b.Len() > 3 {
|
||||
rawBytes[1] = 0
|
||||
rawBytes[2] = 0
|
||||
rawBytes[3] = 0
|
||||
}
|
||||
|
||||
select {
|
||||
case s.bindServer.readQueue <- &netReadInfo{
|
||||
buff: b,
|
||||
endpoint: nep,
|
||||
}:
|
||||
case <-s.bindServer.closedCh:
|
||||
nep.conn = nil
|
||||
return nil
|
||||
buf.ReleaseMulti(mb[i:])
|
||||
return errors.New("bind closed")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,9 +144,11 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
|
||||
// Currently we have no way to link to the original source address
|
||||
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
|
||||
ctx = session.ContextWithInbound(ctx, &inbound)
|
||||
content := new(session.Content)
|
||||
if s.info.contentTag != nil {
|
||||
ctx = session.ContextWithContent(ctx, s.info.contentTag)
|
||||
content.SniffingRequest = s.info.contentTag.SniffingRequest
|
||||
}
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
ctx = session.SubContextFromMuxInbound(ctx)
|
||||
|
||||
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
|
||||
@@ -8,25 +8,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
)
|
||||
|
||||
var wgLogger = &device.Logger{
|
||||
Verbosef: func(format string, args ...any) {
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Debug,
|
||||
Content: fmt.Sprintf(format, args...),
|
||||
})
|
||||
},
|
||||
Errorf: func(format string, args ...any) {
|
||||
log.Record(&log.GeneralMessage{
|
||||
Severity: log.Severity_Error,
|
||||
Content: fmt.Sprintf(format, args...),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
deviceConfig := config.(*DeviceConfig)
|
||||
|
||||
@@ -25,9 +25,11 @@ type Item struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RandMin int64 `protobuf:"varint,1,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
|
||||
RandMax int64 `protobuf:"varint,2,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
|
||||
Packet []byte `protobuf:"bytes,3,opt,name=packet,proto3" json:"packet,omitempty"`
|
||||
DelayMin int64 `protobuf:"varint,4,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
|
||||
DelayMax int64 `protobuf:"varint,5,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
|
||||
RandRangeMin int32 `protobuf:"varint,3,opt,name=rand_range_min,json=randRangeMin,proto3" json:"rand_range_min,omitempty"`
|
||||
RandRangeMax int32 `protobuf:"varint,4,opt,name=rand_range_max,json=randRangeMax,proto3" json:"rand_range_max,omitempty"`
|
||||
Packet []byte `protobuf:"bytes,5,opt,name=packet,proto3" json:"packet,omitempty"`
|
||||
DelayMin int64 `protobuf:"varint,6,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
|
||||
DelayMax int64 `protobuf:"varint,7,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -76,6 +78,20 @@ func (x *Item) GetRandMax() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Item) GetRandRangeMin() int32 {
|
||||
if x != nil {
|
||||
return x.RandRangeMin
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Item) GetRandRangeMax() int32 {
|
||||
if x != nil {
|
||||
return x.RandRangeMax
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Item) GetPacket() []byte {
|
||||
if x != nil {
|
||||
return x.Packet
|
||||
@@ -161,13 +177,15 @@ var File_transport_internet_finalmask_noise_config_proto protoreflect.FileDescri
|
||||
|
||||
const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"/transport/internet/finalmask/noise/config.proto\x12'xray.transport.internet.finalmask.noise\"\x8e\x01\n" +
|
||||
"/transport/internet/finalmask/noise/config.proto\x12'xray.transport.internet.finalmask.noise\"\xda\x01\n" +
|
||||
"\x04Item\x12\x19\n" +
|
||||
"\brand_min\x18\x01 \x01(\x03R\arandMin\x12\x19\n" +
|
||||
"\brand_max\x18\x02 \x01(\x03R\arandMax\x12\x16\n" +
|
||||
"\x06packet\x18\x03 \x01(\fR\x06packet\x12\x1b\n" +
|
||||
"\tdelay_min\x18\x04 \x01(\x03R\bdelayMin\x12\x1b\n" +
|
||||
"\tdelay_max\x18\x05 \x01(\x03R\bdelayMax\"\x87\x01\n" +
|
||||
"\brand_max\x18\x02 \x01(\x03R\arandMax\x12$\n" +
|
||||
"\x0erand_range_min\x18\x03 \x01(\x05R\frandRangeMin\x12$\n" +
|
||||
"\x0erand_range_max\x18\x04 \x01(\x05R\frandRangeMax\x12\x16\n" +
|
||||
"\x06packet\x18\x05 \x01(\fR\x06packet\x12\x1b\n" +
|
||||
"\tdelay_min\x18\x06 \x01(\x03R\bdelayMin\x12\x1b\n" +
|
||||
"\tdelay_max\x18\a \x01(\x03R\bdelayMax\"\x87\x01\n" +
|
||||
"\x06Config\x12\x1b\n" +
|
||||
"\treset_min\x18\x01 \x01(\x03R\bresetMin\x12\x1b\n" +
|
||||
"\treset_max\x18\x02 \x01(\x03R\bresetMax\x12C\n" +
|
||||
|
||||
@@ -9,9 +9,11 @@ option java_multiple_files = true;
|
||||
message Item {
|
||||
int64 rand_min = 1;
|
||||
int64 rand_max = 2;
|
||||
bytes packet = 3;
|
||||
int64 delay_min = 4;
|
||||
int64 delay_max = 5;
|
||||
int32 rand_range_min = 3;
|
||||
int32 rand_range_max = 4;
|
||||
bytes packet = 5;
|
||||
int64 delay_min = 6;
|
||||
int64 delay_max = 7;
|
||||
}
|
||||
|
||||
message Config {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package noise
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
)
|
||||
|
||||
@@ -77,7 +75,7 @@ func (c *noiseConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
for _, item := range c.config.Items {
|
||||
if item.RandMax > 0 {
|
||||
item.Packet = make([]byte, crypto.RandBetween(item.RandMin, item.RandMax))
|
||||
common.Must2(rand.Read(item.Packet))
|
||||
crypto.RandBytesBetween(item.Packet, byte(item.RandRangeMin), byte(item.RandRangeMax))
|
||||
}
|
||||
c.PacketConn.WriteTo(item.Packet, addr)
|
||||
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
|
||||
|
||||
@@ -469,11 +469,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
||||
if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 {
|
||||
err := ApplyECH(c, config)
|
||||
if err != nil {
|
||||
if c.EchForceQuery == "full" {
|
||||
errors.LogError(context.Background(), err)
|
||||
} else {
|
||||
errors.LogInfo(context.Background(), err)
|
||||
}
|
||||
errors.LogError(context.Background(), err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ message Config {
|
||||
|
||||
string ech_config_list = 19;
|
||||
|
||||
// Deprecated
|
||||
string ech_force_query = 20;
|
||||
|
||||
SocketConfig ech_socket_settings = 21;
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
dns2 "github.com/xtls/xray-core/features/dns"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@@ -49,20 +48,10 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
||||
|
||||
// for client
|
||||
if len(c.EchConfigList) != 0 {
|
||||
ECHForceQuery := c.EchForceQuery
|
||||
switch ECHForceQuery {
|
||||
case "none", "half", "full":
|
||||
case "":
|
||||
ECHForceQuery = "full" // default to full
|
||||
default:
|
||||
panic("Invalid ECHForceQuery: " + c.EchForceQuery)
|
||||
}
|
||||
defer func() {
|
||||
// if failed to get ECHConfig, use an invalid one to make connection fail
|
||||
if err != nil || len(ECHConfig) == 0 {
|
||||
if ECHForceQuery == "full" {
|
||||
ECHConfig = []byte{1, 1, 4, 5, 1, 4}
|
||||
}
|
||||
if len(ECHConfig) == 0 {
|
||||
ECHConfig = []byte{1, 1, 4, 5, 1, 4}
|
||||
}
|
||||
config.EncryptedClientHelloConfigList = ECHConfig
|
||||
}()
|
||||
@@ -83,7 +72,7 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
||||
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")
|
||||
}
|
||||
ECHConfig, err = QueryRecord(nameToQuery, DNSServer, c.EchForceQuery, c.EchSocketSettings)
|
||||
ECHConfig, err = QueryRecord(nameToQuery, DNSServer, c.EchSocketSettings)
|
||||
if err != nil {
|
||||
return errors.New("Failed to query ECH DNS record for domain: ", nameToQuery, " at server: ", DNSServer).Base(err)
|
||||
}
|
||||
@@ -107,7 +96,6 @@ type ECHConfigCache struct {
|
||||
type echConfigRecord struct {
|
||||
config []byte
|
||||
expire time.Time
|
||||
err error
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -125,39 +113,34 @@ func ECHCacheKey(server, domain string, sockopt *internet.SocketConfig) string {
|
||||
// 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.
|
||||
// if isLockedUpdate is true, it will not try to acquire the lock.
|
||||
func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) {
|
||||
func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, sockopt *internet.SocketConfig) ([]byte, error) {
|
||||
if !isLockedUpdate {
|
||||
c.UpdateLock.Lock()
|
||||
defer c.UpdateLock.Unlock()
|
||||
}
|
||||
// Double check cache after acquiring lock
|
||||
configRecord := c.configRecord.Load()
|
||||
if configRecord.expire.After(time.Now()) && configRecord.err == nil {
|
||||
if configRecord.expire.After(time.Now()) {
|
||||
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
|
||||
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
|
||||
echConfig, ttl, err := dnsQuery(server, domain, sockopt)
|
||||
// if in "full", directly return
|
||||
if err != nil && forceQuery == "full" {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ttl == 0 {
|
||||
ttl = dns2.DefaultTTL
|
||||
}
|
||||
configRecord = &echConfigRecord{
|
||||
config: echConfig,
|
||||
expire: time.Now().Add(time.Duration(ttl) * time.Second),
|
||||
err: err,
|
||||
}
|
||||
c.configRecord.Store(configRecord)
|
||||
return configRecord.config, configRecord.err
|
||||
return configRecord.config, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func QueryRecord(domain string, server string, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) {
|
||||
func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) ([]byte, error) {
|
||||
GlobalECHConfigCacheKey := ECHCacheKey(server, domain, sockopt)
|
||||
echConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey)
|
||||
if !ok {
|
||||
@@ -166,25 +149,25 @@ func QueryRecord(domain string, server string, forceQuery string, sockopt *inter
|
||||
echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache)
|
||||
}
|
||||
configRecord := echConfigCache.configRecord.Load()
|
||||
if configRecord.expire.After(time.Now()) && (configRecord.err == nil || forceQuery == "none") {
|
||||
if configRecord.expire.After(time.Now()) {
|
||||
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
|
||||
// otherwise return old value immediately and update in a goroutine
|
||||
// but if the cache is too old, wait for update
|
||||
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
||||
return echConfigCache.Update(domain, server, false, forceQuery, sockopt)
|
||||
return echConfigCache.Update(domain, server, false, sockopt)
|
||||
} else {
|
||||
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||
if echConfigCache.UpdateLock.TryLock() {
|
||||
go func() {
|
||||
defer echConfigCache.UpdateLock.Unlock()
|
||||
echConfigCache.Update(domain, server, true, forceQuery, sockopt)
|
||||
echConfigCache.Update(domain, server, true, sockopt)
|
||||
}()
|
||||
}
|
||||
return configRecord.config, configRecord.err
|
||||
return configRecord.config, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,8 +305,7 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b
|
||||
}
|
||||
}
|
||||
}
|
||||
// empty is valid, means no ECH config found
|
||||
return nil, dns2.DefaultTTL, nil
|
||||
return nil, 0, errors.New("no valid ECH config found in DNS response")
|
||||
}
|
||||
|
||||
var ErrInvalidLen = errors.New("goech: invalid length")
|
||||
|
||||
@@ -3,6 +3,7 @@ package tls
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -59,21 +60,11 @@ func TestECHDial(t *testing.T) {
|
||||
func TestECHDialFail(t *testing.T) {
|
||||
config := &Config{
|
||||
ServerName: "cloudflare.com",
|
||||
EchConfigList: "udp://127.0.0.1",
|
||||
EchForceQuery: "half",
|
||||
EchConfigList: "udp://0.0.0.0",
|
||||
}
|
||||
config.GetTLSConfig()
|
||||
// check cache
|
||||
echConfigCache, ok := GlobalECHConfigCache.Load(ECHCacheKey("udp://127.0.0.1", "cloudflare.com", nil))
|
||||
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")
|
||||
tlsConfig := config.GetTLSConfig()
|
||||
ApplyECH(config, tlsConfig)
|
||||
if !slices.Equal(tlsConfig.EncryptedClientHelloConfigList, []byte{1, 1, 4, 5, 1, 4}) {
|
||||
t.Error("ECH config should be invalid when query failed", " but got ", tlsConfig.EncryptedClientHelloConfigList)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user