Compare commits

..
Author SHA1 Message Date
patterniha b668333546 set splice-timeout to 24 hours 2025-08-22 20:27:10 +03:30
patterniha a22c4cf301 fix splice-timeout 2025-08-22 19:04:44 +03:30
patterniha eba1f7258e Timer: prevent creating redundant check task 2025-08-22 09:12:10 +03:30
66 changed files with 778 additions and 2178 deletions
+1 -3
View File
@@ -11,10 +11,8 @@
[<img alt="Project X NFT" width="150px" src="https://raw2.seadn.io/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/7fa9ce900fb39b44226348db330e32/8b7fa9ce900fb39b44226348db330e32.svg" />](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**
- **VLESS NFT: https://opensea.io/collection/vless**
- **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2** - **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2**
- **Related links: [VLESS Post-Quantum Encryption](https://github.com/XTLS/Xray-core/pull/5067), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113), [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)** - **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)**
## License ## License
+3 -47
View File
@@ -29,7 +29,7 @@ var errSniffingTimeout = errors.New("timeout on sniffing")
type cachedReader struct { type cachedReader struct {
sync.Mutex sync.Mutex
reader buf.TimeoutReader // *pipe.Reader or *buf.TimeoutWrapperReader reader *pipe.Reader
cache buf.MultiBuffer cache buf.MultiBuffer
} }
@@ -87,9 +87,7 @@ func (r *cachedReader) Interrupt() {
r.cache = buf.ReleaseMulti(r.cache) r.cache = buf.ReleaseMulti(r.cache)
} }
r.Unlock() r.Unlock()
if p, ok := r.reader.(*pipe.Reader); ok { r.reader.Interrupt()
p.Interrupt()
}
} }
// DefaultDispatcher is a default implementation of Dispatcher. // DefaultDispatcher is a default implementation of Dispatcher.
@@ -196,47 +194,6 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
return inboundLink, outboundLink return inboundLink, outboundLink
} }
func (d *DefaultDispatcher) WrapLink(ctx context.Context, link *transport.Link) *transport.Link {
sessionInbound := session.InboundFromContext(ctx)
var user *protocol.MemoryUser
if sessionInbound != nil {
user = sessionInbound.User
}
link.Reader = &buf.TimeoutWrapperReader{Reader: link.Reader}
if user != nil && len(user.Email) > 0 {
p := d.policy.ForLevel(user.Level)
if p.Stats.UserUplink {
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
}
}
if p.Stats.UserDownlink {
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
link.Writer = &SizeStatWriter{
Counter: c,
Writer: link.Writer,
}
}
}
if p.Stats.UserOnline {
name := "user>>>" + user.Email + ">>>online"
if om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil {
sessionInbounds := session.InboundFromContext(ctx)
userIP := sessionInbounds.Source.Address.String()
om.AddIP(userIP)
// log Online user with ips
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
}
}
}
return link
}
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool { func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
domain := result.Domain() domain := result.Domain()
if domain == "" { if domain == "" {
@@ -357,13 +314,12 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
content = new(session.Content) content = new(session.Content)
ctx = session.ContextWithContent(ctx, content) ctx = session.ContextWithContent(ctx, content)
} }
outbound = d.WrapLink(ctx, outbound)
sniffingRequest := content.SniffingRequest sniffingRequest := content.SniffingRequest
if !sniffingRequest.Enabled { if !sniffingRequest.Enabled {
d.routedDispatch(ctx, outbound, destination) d.routedDispatch(ctx, outbound, destination)
} else { } else {
cReader := &cachedReader{ cReader := &cachedReader{
reader: outbound.Reader.(buf.TimeoutReader), reader: outbound.Reader.(*pipe.Reader),
} }
outbound.Reader = cReader outbound.Reader = cReader
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network) result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
+2 -2
View File
@@ -329,7 +329,7 @@ func init() {
} }
func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) { func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) {
conn4, err4 := net.Dial("udp4", "192.33.4.12:53") conn4, err4 := net.Dial("udp4", "8.8.8.8:53")
if err4 != nil { if err4 != nil {
supportIPv4 = false supportIPv4 = false
} else { } else {
@@ -337,7 +337,7 @@ func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) {
conn4.Close() conn4.Close()
} }
conn6, err6 := net.Dial("udp6", "[2001:500:2::c]:53") conn6, err6 := net.Dial("udp6", "[2001:4860:4860::8888]:53")
if err6 != nil { if err6 != nil {
supportIPv6 = false supportIPv6 = false
} else { } else {
+3 -3
View File
@@ -39,8 +39,8 @@ func Test_parseResponse(t *testing.T) {
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")), common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")), common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")), common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")),
common.Must2(dns.NewRR("google.com. IN AAAA 2001:4860:4860::8888")), common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8888")),
common.Must2(dns.NewRR("google.com. IN AAAA 2001:4860:4860::8844")), common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8844")),
) )
p = append(p, common.Must2(ans.Pack())) p = append(p, common.Must2(ans.Pack()))
@@ -72,7 +72,7 @@ func Test_parseResponse(t *testing.T) {
}, },
{ {
"aaaa record", "aaaa record",
&IPRecord{2, []net.IP{net.ParseIP("2001:4860:4860::8888"), net.ParseIP("2001:4860:4860::8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil}, &IPRecord{2, []net.IP{net.ParseIP("2001::123:8888"), net.ParseIP("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil},
false, false,
}, },
} }
+1 -7
View File
@@ -90,9 +90,7 @@ func (s *ClassicNameServer) RequestsCleanup() error {
// HandleResponse handles udp response packet from remote DNS server. // HandleResponse handles udp response packet from remote DNS server.
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) { func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
payload := packet.Payload ipRec, err := parseResponse(packet.Payload.Bytes())
ipRec, err := parseResponse(payload.Bytes())
payload.Release()
if err != nil { if err != nil {
errors.LogError(ctx, s.Name(), " fail to parse responded DNS udp") errors.LogError(ctx, s.Name(), " fail to parse responded DNS udp")
return return
@@ -127,8 +125,6 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
newReq.msg = &newMsg newReq.msg = &newMsg
s.addPendingRequest(&newReq) s.addPendingRequest(&newReq)
b, _ := dns.PackMessage(newReq.msg) b, _ := dns.PackMessage(newReq.msg)
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b) s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
return return
} }
@@ -162,8 +158,6 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, domai
} }
s.addPendingRequest(udpReq) s.addPendingRequest(udpReq)
b, _ := dns.PackMessage(req.msg) b, _ := dns.PackMessage(req.msg)
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b) s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
} }
} }
+2 -8
View File
@@ -239,10 +239,8 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
} }
out: out:
err := h.proxy.Process(ctx, link, h) err := h.proxy.Process(ctx, link, h)
var errC error
if err != nil { if err != nil {
errC = errors.Cause(err) if goerrors.Is(err, io.EOF) || goerrors.Is(err, io.ErrClosedPipe) || goerrors.Is(err, context.Canceled) {
if goerrors.Is(errC, io.EOF) || goerrors.Is(errC, io.ErrClosedPipe) || goerrors.Is(errC, context.Canceled) {
err = nil err = nil
} }
} }
@@ -253,11 +251,7 @@ out:
errors.LogInfo(ctx, err.Error()) errors.LogInfo(ctx, err.Error())
common.Interrupt(link.Writer) common.Interrupt(link.Writer)
} else { } else {
if errC != nil && goerrors.Is(errC, io.ErrClosedPipe) { common.Close(link.Writer)
common.Interrupt(link.Writer)
} else {
common.Close(link.Writer)
}
} }
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
} }
+16 -16
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"time" "time"
"github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/mux"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
@@ -149,23 +148,25 @@ func (w *BridgeWorker) Connections() uint32 {
} }
func (w *BridgeWorker) handleInternalConn(link *transport.Link) { func (w *BridgeWorker) handleInternalConn(link *transport.Link) {
reader := link.Reader go func() {
for { reader := link.Reader
mb, err := reader.ReadMultiBuffer() for {
if err != nil { mb, err := reader.ReadMultiBuffer()
break if err != nil {
}
for _, b := range mb {
var ctl Control
if err := proto.Unmarshal(b.Bytes(), &ctl); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to parse proto message")
break break
} }
if ctl.State != w.state { for _, b := range mb {
w.state = ctl.State var ctl Control
if err := proto.Unmarshal(b.Bytes(), &ctl); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to parse proto message")
break
}
if ctl.State != w.state {
w.state = ctl.State
}
} }
} }
} }()
} }
func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {
@@ -180,7 +181,7 @@ func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*tra
uplinkReader, uplinkWriter := pipe.New(opt...) uplinkReader, uplinkWriter := pipe.New(opt...)
downlinkReader, downlinkWriter := pipe.New(opt...) downlinkReader, downlinkWriter := pipe.New(opt...)
go w.handleInternalConn(&transport.Link{ w.handleInternalConn(&transport.Link{
Reader: downlinkReader, Reader: downlinkReader,
Writer: uplinkWriter, Writer: uplinkWriter,
}) })
@@ -199,7 +200,6 @@ func (w *BridgeWorker) DispatchLink(ctx context.Context, dest net.Destination, l
return w.dispatcher.DispatchLink(ctx, dest, link) return w.dispatcher.DispatchLink(ctx, dest, link)
} }
link = w.dispatcher.(*dispatcher.DefaultDispatcher).WrapLink(ctx, link)
w.handleInternalConn(link) w.handleInternalConn(link)
return nil return nil
+5 -3
View File
@@ -15,6 +15,8 @@ const (
var ErrBufferFull = errors.New("buffer is full") var ErrBufferFull = errors.New("buffer is full")
var zero = [Size * 10]byte{0}
var pool = bytespool.GetPool(Size) var pool = bytespool.GetPool(Size)
// ownership represents the data owner of the buffer. // ownership represents the data owner of the buffer.
@@ -144,7 +146,7 @@ func (b *Buffer) Bytes() []byte {
} }
// Extend increases the buffer size by n bytes, and returns the extended part. // Extend increases the buffer size by n bytes, and returns the extended part.
// It panics if result size is larger than size of this buffer. // It panics if result size is larger than buf.Size.
func (b *Buffer) Extend(n int32) []byte { func (b *Buffer) Extend(n int32) []byte {
end := b.end + n end := b.end + n
if end > int32(len(b.v)) { if end > int32(len(b.v)) {
@@ -152,7 +154,7 @@ func (b *Buffer) Extend(n int32) []byte {
} }
ext := b.v[b.end:end] ext := b.v[b.end:end]
b.end = end b.end = end
clear(ext) copy(ext, zero[:])
return ext return ext
} }
@@ -215,7 +217,7 @@ func (b *Buffer) Resize(from, to int32) {
b.start += from b.start += from
b.Check() b.Check()
if b.end > oldEnd { if b.end > oldEnd {
clear(b.v[oldEnd:b.end]) copy(b.v[oldEnd:b.end], zero[:])
} }
} }
-50
View File
@@ -24,59 +24,9 @@ var ErrReadTimeout = errors.New("IO timeout")
// TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout. // TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout.
type TimeoutReader interface { type TimeoutReader interface {
Reader
ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error) ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error)
} }
type TimeoutWrapperReader struct {
Reader
stats.Counter
mb MultiBuffer
err error
done chan struct{}
}
func (r *TimeoutWrapperReader) ReadMultiBuffer() (MultiBuffer, error) {
if r.done != nil {
<-r.done
r.done = nil
if r.Counter != nil {
r.Counter.Add(int64(r.mb.Len()))
}
return r.mb, r.err
}
r.mb, r.err = r.Reader.ReadMultiBuffer()
if r.Counter != nil {
r.Counter.Add(int64(r.mb.Len()))
}
return r.mb, r.err
}
func (r *TimeoutWrapperReader) ReadMultiBufferTimeout(duration time.Duration) (MultiBuffer, error) {
if r.done == nil {
r.done = make(chan struct{})
go func() {
r.mb, r.err = r.Reader.ReadMultiBuffer()
close(r.done)
}()
}
timeout := make(chan struct{})
go func() {
time.Sleep(duration)
close(timeout)
}()
select {
case <-r.done:
r.done = nil
if r.Counter != nil {
r.Counter.Add(int64(r.mb.Len()))
}
return r.mb, r.err
case <-timeout:
return nil, nil
}
}
// Writer extends io.Writer with MultiBuffer. // Writer extends io.Writer with MultiBuffer.
type Writer interface { type Writer interface {
// WriteMultiBuffer writes a MultiBuffer into underlying writer. // WriteMultiBuffer writes a MultiBuffer into underlying writer.
+3 -17
View File
@@ -75,10 +75,9 @@ func (w *BufferToBytesWriter) ReadFrom(reader io.Reader) (int64, error) {
// BufferedWriter is a Writer with internal buffer. // BufferedWriter is a Writer with internal buffer.
type BufferedWriter struct { type BufferedWriter struct {
sync.Mutex sync.Mutex
writer Writer writer Writer
buffer *Buffer buffer *Buffer
buffered bool buffered bool
flushNext bool
} }
// NewBufferedWriter creates a new BufferedWriter. // NewBufferedWriter creates a new BufferedWriter.
@@ -162,12 +161,6 @@ func (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error {
} }
} }
if w.flushNext {
w.buffered = false
w.flushNext = false
return w.flushInternal()
}
return nil return nil
} }
@@ -208,13 +201,6 @@ func (w *BufferedWriter) SetBuffered(f bool) error {
return nil return nil
} }
// SetFlushNext will wait the next WriteMultiBuffer to flush and set buffered = false
func (w *BufferedWriter) SetFlushNext() {
w.Lock()
defer w.Unlock()
w.flushNext = true
}
// ReadFrom implements io.ReaderFrom. // ReadFrom implements io.ReaderFrom.
func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) { func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) {
if err := w.SetBuffered(false); err != nil { if err := w.SetBuffered(false); err != nil {
-12
View File
@@ -7,7 +7,6 @@ import (
"go/build" "go/build"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -154,14 +153,3 @@ func GetModuleName(pathToProjectRoot string) (string, error) {
} }
return moduleName, fmt.Errorf("no `go.mod` file in every parent directory of `%s`", pathToProjectRoot) return moduleName, fmt.Errorf("no `go.mod` file in every parent directory of `%s`", pathToProjectRoot)
} }
// CloseIfExists call obj.Close() if obj is not nil.
func CloseIfExists(obj any) error {
if obj != nil {
v := reflect.ValueOf(obj)
if !v.IsNil() {
return Close(obj)
}
}
return nil
}
-3
View File
@@ -10,9 +10,6 @@ func RandBetween(from int64, to int64) int64 {
if from == to { if from == to {
return from return from
} }
if from > to {
from, to = to, from
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
return from + bigInt.Int64() return from + bigInt.Int64()
} }
+5 -14
View File
@@ -2,7 +2,6 @@ package mux
import ( import (
"context" "context"
goerrors "errors"
"io" "io"
"sync" "sync"
"time" "time"
@@ -155,11 +154,8 @@ func (f *DialingWorkerFactory) Create() (*ClientWorker, error) {
ctx := session.ContextWithOutbounds(context.Background(), outbounds) ctx := session.ContextWithOutbounds(context.Background(), outbounds)
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
if errP := p.Process(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter}, d); errP != nil { if err := p.Process(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter}, d); err != nil {
errC := errors.Cause(errP) errors.LogInfoInner(ctx, err, "failed to handler mux client connection")
if !(goerrors.Is(errC, io.EOF) || goerrors.Is(errC, io.ErrClosedPipe) || goerrors.Is(errC, context.Canceled)) {
errors.LogInfoInner(ctx, errP, "failed to handler mux client connection")
}
} }
common.Must(c.Close()) common.Must(c.Close())
cancel() cancel()
@@ -226,7 +222,7 @@ func (m *ClientWorker) monitor() {
select { select {
case <-m.done.Wait(): case <-m.done.Wait():
m.sessionManager.Close() m.sessionManager.Close()
common.Interrupt(m.link.Writer) common.Close(m.link.Writer)
common.Interrupt(m.link.Reader) common.Interrupt(m.link.Reader)
return return
case <-m.timer.C: case <-m.timer.C:
@@ -251,7 +247,7 @@ func writeFirstPayload(reader buf.Reader, writer *Writer) error {
return nil return nil
} }
func fetchInput(ctx context.Context, s *Session, output buf.Writer, timer *time.Ticker) { func fetchInput(ctx context.Context, s *Session, output buf.Writer) {
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
transferType := protocol.TransferTypeStream transferType := protocol.TransferTypeStream
@@ -262,7 +258,6 @@ func fetchInput(ctx context.Context, s *Session, output buf.Writer, timer *time.
writer := NewWriter(s.ID, ob.Target, output, transferType, xudp.GetGlobalID(ctx)) writer := NewWriter(s.ID, ob.Target, output, transferType, xudp.GetGlobalID(ctx))
defer s.Close(false) defer s.Close(false)
defer writer.Close() defer writer.Close()
defer timer.Reset(time.Second * 16)
errors.LogInfo(ctx, "dispatching request to ", ob.Target) errors.LogInfo(ctx, "dispatching request to ", ob.Target)
if err := writeFirstPayload(s.input, writer); err != nil { if err := writeFirstPayload(s.input, writer); err != nil {
@@ -312,11 +307,7 @@ func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool
} }
s.input = link.Reader s.input = link.Reader
s.output = link.Writer s.output = link.Writer
if _, ok := link.Reader.(*pipe.Reader); ok { go fetchInput(ctx, s, m.link.Writer)
go fetchInput(ctx, s, m.link.Writer, m.timer)
} else {
fetchInput(ctx, s, m.link.Writer, m.timer)
}
return true return true
} }
+2 -11
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"io" "io"
"github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -62,7 +61,6 @@ func (s *Server) DispatchLink(ctx context.Context, dest net.Destination, link *t
if dest.Address != muxCoolAddress { if dest.Address != muxCoolAddress {
return s.dispatcher.DispatchLink(ctx, dest, link) return s.dispatcher.DispatchLink(ctx, dest, link)
} }
link = s.dispatcher.(*dispatcher.DefaultDispatcher).WrapLink(ctx, link)
_, err := NewServerWorker(ctx, s.dispatcher, link) _, err := NewServerWorker(ctx, s.dispatcher, link)
return err return err
} }
@@ -89,14 +87,7 @@ func NewServerWorker(ctx context.Context, d routing.Dispatcher, link *transport.
link: link, link: link,
sessionManager: NewSessionManager(), sessionManager: NewSessionManager(),
} }
if inbound := session.InboundFromContext(ctx); inbound != nil { go worker.run(ctx)
inbound.CanSpliceCopy = 3
}
if _, ok := link.Reader.(*pipe.Reader); ok {
go worker.run(ctx)
} else {
worker.run(ctx)
}
return worker, nil return worker, nil
} }
@@ -320,8 +311,8 @@ func (w *ServerWorker) run(ctx context.Context) {
reader := &buf.BufferedReader{Reader: w.link.Reader} reader := &buf.BufferedReader{Reader: w.link.Reader}
defer w.sessionManager.Close() defer w.sessionManager.Close()
defer common.Close(w.link.Writer)
defer common.Interrupt(w.link.Reader) defer common.Interrupt(w.link.Reader)
defer common.Interrupt(w.link.Writer)
for { for {
select { select {
+8 -6
View File
@@ -79,18 +79,20 @@ type CommandSwitchAccount struct {
} }
var ( var (
// Keep in sync with crypto/tls/cipher_suites.go. hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH // Keep in sync with crypto/aes/cipher_s390x.go.
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64 hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
) )
func (sc *SecurityConfig) GetSecurityType() SecurityType { func (sc *SecurityConfig) GetSecurityType() SecurityType {
if sc == nil || sc.Type == SecurityType_AUTO { if sc == nil || sc.Type == SecurityType_AUTO {
if HasAESGCMHardwareSupport { if hasAESGCMHardwareSupport {
return SecurityType_AES128_GCM return SecurityType_AES128_GCM
} }
return SecurityType_CHACHA20_POLY1305 return SecurityType_CHACHA20_POLY1305
-34
View File
@@ -1,34 +0,0 @@
package http
import (
"context"
gohttp "net/http"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
)
// NewClient creates an HTTP client with with internal dialer and using the given sockopt.
// sockopt can only have one or empty.
func NewClient(sockopt ...*internet.SocketConfig) *gohttp.Client {
var Sockopt *internet.SocketConfig
switch len(sockopt) {
case 0:
case 1:
Sockopt = sockopt[0]
default:
panic("sockopt can only be nil or have one")
}
httpClient := &gohttp.Client{
Transport: &gohttp.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dest, err := net.ParseDestination(network + ":" + addr)
if err != nil {
return nil, err
}
return internet.DialSystem(ctx, dest, Sockopt)
},
},
}
return httpClient
}
+43 -28
View File
@@ -3,7 +3,6 @@ package signal
import ( import (
"context" "context"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@@ -15,12 +14,11 @@ type ActivityUpdater interface {
} }
type ActivityTimer struct { type ActivityTimer struct {
mu sync.RWMutex sync.RWMutex
updated chan struct{} updated chan struct{}
checkTask *task.Periodic checkTask *task.Periodic
onTimeout func() onTimeout func()
consumed atomic.Bool overridden bool
once sync.Once
} }
func (t *ActivityTimer) Update() { func (t *ActivityTimer) Update() {
@@ -34,45 +32,62 @@ func (t *ActivityTimer) check() error {
select { select {
case <-t.updated: case <-t.updated:
default: default:
t.finish() t.finish(false)
} }
return nil return nil
} }
func (t *ActivityTimer) finish() { func (t *ActivityTimer) finish(locked bool) {
t.once.Do(func() { if !locked {
t.consumed.Store(true) t.Lock()
t.mu.Lock() defer t.Unlock()
defer t.mu.Unlock() }
common.CloseIfExists(t.checkTask) if t.onTimeout != nil {
t.onTimeout() t.onTimeout()
}) t.onTimeout = nil
}
if t.checkTask != nil {
t.checkTask.Close()
t.checkTask = nil
}
} }
func (t *ActivityTimer) SetTimeout(timeout time.Duration) { func (t *ActivityTimer) setTimeout(timeout time.Duration) {
if t.consumed.Load() { if t.onTimeout == nil {
return return
} }
if timeout == 0 { if timeout == 0 {
t.finish() t.finish(true)
return return
} }
t.mu.Lock() checkTask := &task.Periodic{
defer t.mu.Unlock()
// double check, just in case
if t.consumed.Load() {
return
}
newCheckTask := &task.Periodic{
Interval: timeout, Interval: timeout,
Execute: t.check, Execute: t.check,
} }
common.CloseIfExists(t.checkTask)
t.checkTask = newCheckTask if t.checkTask != nil {
t.checkTask.Close()
t.overridden = true
}
t.checkTask = checkTask
t.Update() t.Update()
common.Must(newCheckTask.Start()) common.Must(checkTask.Start())
}
func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
t.Lock()
t.setTimeout(timeout)
t.Unlock()
}
func (t *ActivityTimer) SetTimeoutIfNotOverridden(timeout time.Duration) {
t.Lock()
if !t.overridden {
t.setTimeout(timeout)
}
t.Unlock()
} }
func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer { func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
+2 -2
View File
@@ -18,8 +18,8 @@ import (
var ( var (
Version_x byte = 25 Version_x byte = 25
Version_y byte = 9 Version_y byte = 8
Version_z byte = 5 Version_z byte = 3
) )
var ( var (
+2 -2
View File
@@ -16,10 +16,10 @@ require (
github.com/sagernet/sing v0.5.1 github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.10.0
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.41.0 golang.org/x/crypto v0.41.0
golang.org/x/net v0.43.0 golang.org/x/net v0.43.0
+4 -4
View File
@@ -67,16 +67,16 @@ github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f h1:o1Kryl9qEYYzNep9RId9DM1kBn8tBrcK5UJnti/l0NI= github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 h1:Ript0vN+nSO33+Vj4n0mgNY5M+oOxFQJdrJ1VnwTBO0=
github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+10 -101
View File
@@ -1,7 +1,6 @@
package conf package conf
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"path/filepath" "path/filepath"
"runtime" "runtime"
@@ -74,67 +73,17 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
} }
if account.Encryption != "" { if account.Encryption != "" {
return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`) return nil, errors.New(`VLESS clients: "encryption" should not in inbound settings`)
} }
user.Account = serial.ToTypedMessage(account) user.Account = serial.ToTypedMessage(account)
config.Clients[idx] = user config.Clients[idx] = user
} }
if c.Decryption != "none" {
return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`)
}
config.Decryption = c.Decryption config.Decryption = c.Decryption
if !func() bool {
s := strings.Split(config.Decryption, ".")
if len(s) < 4 || s[0] != "mlkem768x25519plus" {
return false
}
switch s[1] {
case "native":
case "xorpub":
config.XorMode = 1
case "random":
config.XorMode = 2
default:
return false
}
t := strings.SplitN(strings.TrimSuffix(s[2], "s"), "-", 2)
i, err := strconv.Atoi(t[0])
if err != nil {
return false
}
config.SecondsFrom = int64(i)
if len(t) == 2 {
i, err := strconv.Atoi(t[1])
if err != nil {
return false
}
config.SecondsTo = int64(i)
}
padding := 0
for _, r := range s[3:] {
if len(r) < 20 {
padding += len(r) + 1
continue
}
if b, _ := base64.RawURLEncoding.DecodeString(r); len(b) != 32 && len(b) != 64 {
return false
}
}
config.Decryption = config.Decryption[27+len(s[2]):]
if padding > 0 {
config.Padding = config.Decryption[:padding-1]
config.Decryption = config.Decryption[padding:]
}
return true
}() && config.Decryption != "none" {
if config.Decryption == "" {
return nil, errors.New(`VLESS settings: please add/set "decryption":"none" to every settings`)
}
return nil, errors.New(`VLESS settings: unsupported "decryption": ` + config.Decryption)
}
if config.Decryption != "none" && c.Fallbacks != nil {
return nil, errors.New(`VLESS settings: "fallbacks" can not be used together with "decryption"`)
}
for _, fb := range c.Fallbacks { for _, fb := range c.Fallbacks {
var i uint16 var i uint16
@@ -206,16 +155,16 @@ type VLessOutboundConfig struct {
func (c *VLessOutboundConfig) Build() (proto.Message, error) { func (c *VLessOutboundConfig) Build() (proto.Message, error) {
config := new(outbound.Config) config := new(outbound.Config)
if len(c.Vnext) != 1 { if len(c.Vnext) == 0 {
return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`) return nil, errors.New(`VLESS settings: "vnext" is empty`)
} }
config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext)) config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext))
for idx, rec := range c.Vnext { for idx, rec := range c.Vnext {
if rec.Address == nil { if rec.Address == nil {
return nil, errors.New(`VLESS vnext: "address" is not set`) return nil, errors.New(`VLESS vnext: "address" is not set`)
} }
if len(rec.Users) != 1 { if len(rec.Users) == 0 {
return nil, errors.New(`VLESS vnext: "users" should have one and only one member`) return nil, errors.New(`VLESS vnext: "users" is empty`)
} }
spec := &protocol.ServerEndpoint{ spec := &protocol.ServerEndpoint{
Address: rec.Address.Build(), Address: rec.Address.Build(),
@@ -244,48 +193,8 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`) return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
} }
if !func() bool { if account.Encryption != "none" {
s := strings.Split(account.Encryption, ".") return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`)
if len(s) < 4 || s[0] != "mlkem768x25519plus" {
return false
}
switch s[1] {
case "native":
case "xorpub":
account.XorMode = 1
case "random":
account.XorMode = 2
default:
return false
}
switch s[2] {
case "1rtt":
case "0rtt":
account.Seconds = 1
default:
return false
}
padding := 0
for _, r := range s[3:] {
if len(r) < 20 {
padding += len(r) + 1
continue
}
if b, _ := base64.RawURLEncoding.DecodeString(r); len(b) != 32 && len(b) != 1184 {
return false
}
}
account.Encryption = account.Encryption[27+len(s[2]):]
if padding > 0 {
account.Padding = account.Encryption[:padding-1]
account.Encryption = account.Encryption[padding:]
}
return true
}() && account.Encryption != "none" {
if account.Encryption == "" {
return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`)
}
return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption)
} }
user.Account = serial.ToTypedMessage(account) user.Account = serial.ToTypedMessage(account)
-2
View File
@@ -17,7 +17,5 @@ func init() {
cmdX25519, cmdX25519,
cmdWG, cmdWG,
cmdMLDSA65, cmdMLDSA65,
cmdMLKEM768,
cmdVLESSEnc,
) )
} }
+10 -45
View File
@@ -3,7 +3,6 @@ package convert
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"github.com/xtls/xray-core/common/cmdarg" "github.com/xtls/xray-core/common/cmdarg"
creflect "github.com/xtls/xray-core/common/reflect" creflect "github.com/xtls/xray-core/common/reflect"
@@ -15,18 +14,15 @@ import (
var cmdProtobuf = &base.Command{ var cmdProtobuf = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} convert pb [-outpbfile file] [-debug] [-type] [json file] [json file] ...", UsageLine: "{{.Exec}} convert pb [-debug] [-type] [json file] [json file] ...",
Short: "Convert multiple json configs to protobuf", Short: "Convert multiple json configs to protobuf",
Long: ` Long: `
Convert multiple configs to ProtoBuf. JSON, YAML and TOML can be used. Convert multiple json configs to protobuf.
Arguments: Arguments:
-o file, -outpbfile file
Write the ProtoBuf output (eg. mix.pb) to specified file location.
-d, -debug -d, -debug
Show mix.pb as JSON format. Show mix.pb as json.
FOR DEBUGGING ONLY! FOR DEBUGGING ONLY!
DO NOT PASS THIS OUTPUT TO XRAY-CORE! DO NOT PASS THIS OUTPUT TO XRAY-CORE!
@@ -35,20 +31,16 @@ Arguments:
Examples: Examples:
{{.Exec}} convert pb -outpbfile output.pb config.json c1.json c2.json c3.json {{.Exec}} convert pb config.json c1.json c2.json c3.json > mix.pb
{{.Exec}} convert pb -debug mix.pb
`, `,
Run: executeConvertConfigsToProtobuf, Run: executeConvertConfigsToProtobuf,
} }
func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) { func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
var optFile string
var optDump bool var optDump bool
var optType bool var optType bool
cmd.Flag.StringVar(&optFile, "o", "", "")
cmd.Flag.StringVar(&optFile, "outpbfile", "", "")
cmd.Flag.BoolVar(&optDump, "d", false, "") cmd.Flag.BoolVar(&optDump, "d", false, "")
cmd.Flag.BoolVar(&optDump, "debug", false, "") cmd.Flag.BoolVar(&optDump, "debug", false, "")
cmd.Flag.BoolVar(&optType, "t", false, "") cmd.Flag.BoolVar(&optType, "t", false, "")
@@ -60,17 +52,6 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
unnamedArgs.Set(v) unnamedArgs.Set(v)
} }
if len(optFile) > 0 {
switch core.GetFormatByExtension(getFileExtension(optFile)){
case "protobuf", "":
fmt.Println("Output ProtoBuf file is ", optFile)
default:
base.Fatalf("-outpbfile followed by a possible original config.")
}
} else if !optDump {
base.Fatalf("-outpbfile not specified")
}
if len(unnamedArgs) < 1 { if len(unnamedArgs) < 1 {
base.Fatalf("invalid config list length: %d", len(unnamedArgs)) base.Fatalf("invalid config list length: %d", len(unnamedArgs))
} }
@@ -89,28 +70,12 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
} }
} }
if len(optFile) > 0 { bytesConfig, err := proto.Marshal(pbConfig)
bytesConfig, err := proto.Marshal(pbConfig) if err != nil {
if err != nil { base.Fatalf("failed to marshal proto config: %s", err)
base.Fatalf("failed to marshal proto config: %s", err) }
}
f, err := os.Create(optFile) if _, err := os.Stdout.Write(bytesConfig); err != nil {
if err != nil { base.Fatalf("failed to write proto config: %s", err)
base.Fatalf("failed to create proto file: %s", err)
}
defer f.Close()
if _, err := f.Write(bytesConfig); err != nil {
base.Fatalf("failed to write proto file: %s", err)
}
} }
} }
func getFileExtension(filename string) string {
idx := strings.LastIndexByte(filename, '.')
if idx == -1 {
return ""
}
return filename[idx+1:]
}
+27 -32
View File
@@ -1,15 +1,17 @@
package all package all
import ( import (
"crypto/ecdh"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"lukechampine.com/blake3" "golang.org/x/crypto/curve25519"
) )
func Curve25519Genkey(StdEncoding bool, input_base64 string) { func Curve25519Genkey(StdEncoding bool, input_base64 string) {
var output string
var err error
var privateKey, publicKey []byte
var encoding *base64.Encoding var encoding *base64.Encoding
if *input_stdEncoding || StdEncoding { if *input_stdEncoding || StdEncoding {
encoding = base64.StdEncoding encoding = base64.StdEncoding
@@ -17,47 +19,40 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
encoding = base64.RawURLEncoding encoding = base64.RawURLEncoding
} }
var privateKey []byte
if len(input_base64) > 0 { if len(input_base64) > 0 {
privateKey, _ = encoding.DecodeString(input_base64) privateKey, err = encoding.DecodeString(input_base64)
if len(privateKey) != 32 { if err != nil {
fmt.Println("Invalid length of X25519 private key.") output = err.Error()
return goto out
}
if len(privateKey) != curve25519.ScalarSize {
output = "Invalid length of private key."
goto out
} }
} }
privateKey, password, hash32, err := genCurve25519(privateKey)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("PrivateKey: %v\nPassword: %v\nHash32: %v\n",
encoding.EncodeToString(privateKey),
encoding.EncodeToString(password),
encoding.EncodeToString(hash32[:]))
}
func genCurve25519(inputPrivateKey []byte) (privateKey []byte, password []byte, hash32 [32]byte, returnErr error) {
if len(inputPrivateKey) > 0 {
privateKey = inputPrivateKey
}
if privateKey == nil { if privateKey == nil {
privateKey = make([]byte, 32) privateKey = make([]byte, curve25519.ScalarSize)
rand.Read(privateKey) if _, err = rand.Read(privateKey); err != nil {
output = err.Error()
goto out
}
} }
// Modify random bytes using algorithm described at: // Modify random bytes using algorithm described at:
// https://cr.yp.to/ecdh.html // https://cr.yp.to/ecdh.html.
// (Just to make sure printing the real private key)
privateKey[0] &= 248 privateKey[0] &= 248
privateKey[31] &= 127 privateKey[31] &= 127
privateKey[31] |= 64 privateKey[31] |= 64
key, err := ecdh.X25519().NewPrivateKey(privateKey) if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
if err != nil { output = err.Error()
returnErr = err goto out
return
} }
password = key.PublicKey().Bytes()
hash32 = blake3.Sum256(password) output = fmt.Sprintf("Private key: %v\nPublic key: %v",
return encoding.EncodeToString(privateKey),
encoding.EncodeToString(publicKey))
out:
fmt.Println(output)
} }
+6 -10
View File
@@ -11,9 +11,9 @@ import (
var cmdMLDSA65 = &base.Command{ var cmdMLDSA65 = &base.Command{
UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`, UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`,
Short: `Generate key pair for ML-DSA-65 post-quantum signature (REALITY)`, Short: `Generate key pair for ML-DSA-65 post-quantum signature`,
Long: ` Long: `
Generate key pair for ML-DSA-65 post-quantum signature (REALITY). Generate key pair for ML-DSA-65 post-quantum signature.
Random: {{.Exec}} mldsa65 Random: {{.Exec}} mldsa65
@@ -25,22 +25,18 @@ func init() {
cmdMLDSA65.Run = executeMLDSA65 // break init loop cmdMLDSA65.Run = executeMLDSA65 // break init loop
} }
var input_mldsa65 = cmdMLDSA65.Flag.String("i", "", "") var input_seed = cmdMLDSA65.Flag.String("i", "", "")
func executeMLDSA65(cmd *base.Command, args []string) { func executeMLDSA65(cmd *base.Command, args []string) {
var seed [32]byte var seed [32]byte
if len(*input_mldsa65) > 0 { if len(*input_seed) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_mldsa65) s, _ := base64.RawURLEncoding.DecodeString(*input_seed)
if len(s) != 32 {
fmt.Println("Invalid length of ML-DSA-65 seed.")
return
}
seed = [32]byte(s) seed = [32]byte(s)
} else { } else {
rand.Read(seed[:]) rand.Read(seed[:])
} }
pub, _ := mldsa65.NewKeyFromSeed(&seed) pub, _ := mldsa65.NewKeyFromSeed(&seed)
fmt.Printf("Seed: %v\nVerify: %v\n", fmt.Printf("Seed: %v\nVerify: %v",
base64.RawURLEncoding.EncodeToString(seed[:]), base64.RawURLEncoding.EncodeToString(seed[:]),
base64.RawURLEncoding.EncodeToString(pub.Bytes())) base64.RawURLEncoding.EncodeToString(pub.Bytes()))
} }
-60
View File
@@ -1,60 +0,0 @@
package all
import (
"crypto/mlkem"
"crypto/rand"
"encoding/base64"
"fmt"
"github.com/xtls/xray-core/main/commands/base"
"lukechampine.com/blake3"
)
var cmdMLKEM768 = &base.Command{
UsageLine: `{{.Exec}} mlkem768 [-i "seed (base64.RawURLEncoding)"]`,
Short: `Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS Encryption)`,
Long: `
Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS Encryption).
Random: {{.Exec}} mlkem768
From seed: {{.Exec}} mlkem768 -i "seed (base64.RawURLEncoding)"
`,
}
func init() {
cmdMLKEM768.Run = executeMLKEM768 // break init loop
}
var input_mlkem768 = cmdMLKEM768.Flag.String("i", "", "")
func executeMLKEM768(cmd *base.Command, args []string) {
var seed [64]byte
if len(*input_mlkem768) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_mlkem768)
if len(s) != 64 {
fmt.Println("Invalid length of ML-KEM-768 seed.")
return
}
seed = [64]byte(s)
} else {
rand.Read(seed[:])
}
seed, client, hash32 := genMLKEM768(&seed)
fmt.Printf("Seed: %v\nClient: %v\nHash32: %v\n",
base64.RawURLEncoding.EncodeToString(seed[:]),
base64.RawURLEncoding.EncodeToString(client),
base64.RawURLEncoding.EncodeToString(hash32[:]))
}
func genMLKEM768(inputSeed *[64]byte) (seed [64]byte, client []byte, hash32 [32]byte) {
if inputSeed == nil {
rand.Read(seed[:])
} else {
seed = *inputSeed
}
key, _ := mlkem.NewDecapsulationKey768(seed[:])
client = key.EncapsulationKey().Bytes()
hash32 = blake3.Sum256(client)
return
}
+2 -2
View File
@@ -9,9 +9,9 @@ import (
var cmdUUID = &base.Command{ var cmdUUID = &base.Command{
UsageLine: `{{.Exec}} uuid [-i "example"]`, UsageLine: `{{.Exec}} uuid [-i "example"]`,
Short: `Generate UUIDv4 or UUIDv5 (VLESS)`, Short: `Generate UUIDv4 or UUIDv5`,
Long: ` Long: `
Generate UUIDv4 or UUIDv5 (VLESS). Generate UUIDv4 or UUIDv5.
UUIDv4 (random): {{.Exec}} uuid UUIDv4 (random): {{.Exec}} uuid
-41
View File
@@ -1,41 +0,0 @@
package all
import (
"encoding/base64"
"fmt"
"strings"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdVLESSEnc = &base.Command{
UsageLine: `{{.Exec}} vlessenc`,
Short: `Generate decryption/encryption json pair (VLESS Encryption)`,
Long: `
Generate decryption/encryption json pair (VLESS Encryption).
`,
}
func init() {
cmdVLESSEnc.Run = executeVLESSEnc // break init loop
}
func executeVLESSEnc(cmd *base.Command, args []string) {
privateKey, password, _, _ := genCurve25519(nil)
serverKey := base64.RawURLEncoding.EncodeToString(privateKey)
clientKey := base64.RawURLEncoding.EncodeToString(password)
decryption := generateDotConfig("mlkem768x25519plus", "native", "600s", serverKey)
encryption := generateDotConfig("mlkem768x25519plus", "native", "0rtt", clientKey)
seed, client, _ := genMLKEM768(nil)
serverKeyPQ := base64.RawURLEncoding.EncodeToString(seed[:])
clientKeyPQ := base64.RawURLEncoding.EncodeToString(client)
decryptionPQ := generateDotConfig("mlkem768x25519plus", "native", "600s", serverKeyPQ)
encryptionPQ := generateDotConfig("mlkem768x25519plus", "native", "0rtt", clientKeyPQ)
fmt.Printf("Choose one Authentication to use, do not mix them. Ephemeral key exchange is Post-Quantum safe anyway.\n\n")
fmt.Printf("Authentication: X25519, not Post-Quantum\n\"decryption\": \"%v\"\n\"encryption\": \"%v\"\n\n", decryption, encryption)
fmt.Printf("Authentication: ML-KEM-768, Post-Quantum\n\"decryption\": \"%v\"\n\"encryption\": \"%v\"\n", decryptionPQ, encryptionPQ)
}
func generateDotConfig(fields ...string) string {
return strings.Join(fields, ".")
}
+2 -2
View File
@@ -6,9 +6,9 @@ import (
var cmdWG = &base.Command{ var cmdWG = &base.Command{
UsageLine: `{{.Exec}} wg [-i "private key (base64.StdEncoding)"]`, UsageLine: `{{.Exec}} wg [-i "private key (base64.StdEncoding)"]`,
Short: `Generate key pair for X25519 key exchange (WireGuard)`, Short: `Generate key pair for wireguard key exchange`,
Long: ` Long: `
Generate key pair for X25519 key exchange (WireGuard). Generate key pair for wireguard key exchange.
Random: {{.Exec}} wg Random: {{.Exec}} wg
+2 -2
View File
@@ -6,9 +6,9 @@ import (
var cmdX25519 = &base.Command{ var cmdX25519 = &base.Command{
UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`, UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`,
Short: `Generate key pair for X25519 key exchange (REALITY, VLESS Encryption)`, Short: `Generate key pair for x25519 key exchange`,
Long: ` Long: `
Generate key pair for X25519 key exchange (REALITY, VLESS Encryption). Generate key pair for x25519 key exchange.
Random: {{.Exec}} x25519 Random: {{.Exec}} x25519
+5 -8
View File
@@ -182,15 +182,12 @@ func getConfigFilePath(verbose bool) cmdarg.Arg {
} }
if workingDir, err := os.Getwd(); err == nil { if workingDir, err := os.Getwd(); err == nil {
suffixes := []string{".json", ".jsonc", ".toml", ".yaml", ".yml"} configFile := filepath.Join(workingDir, "config.json")
for _, suffix := range suffixes { if fileExists(configFile) {
configFile := filepath.Join(workingDir, "config"+suffix) if verbose {
if fileExists(configFile) { log.Println("Using default config: ", configFile)
if verbose {
log.Println("Using default config: ", configFile)
}
return cmdarg.Arg{configFile}
} }
return cmdarg.Arg{configFile}
} }
} }
+21 -51
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
go_errors "errors" go_errors "errors"
"io" "io"
"strings"
"sync" "sync"
"time" "time"
@@ -169,15 +168,11 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
} }
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
terminate := func() { timer := signal.CancelAfterInactivity(ctx, cancel, h.timeout)
cancel()
conn.Close()
}
timer := signal.CancelAfterInactivity(ctx, terminate, h.timeout)
defer timer.SetTimeout(0)
request := func() error { request := func() error {
defer timer.SetTimeout(0) defer conn.Close()
for { for {
b, err := reader.ReadMessage() b, err := reader.ReadMessage()
if err == io.EOF { if err == io.EOF {
@@ -195,33 +190,24 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
if len(h.blockTypes) > 0 { if len(h.blockTypes) > 0 {
for _, blocktype := range h.blockTypes { for _, blocktype := range h.blockTypes {
if blocktype == int32(qType) { if blocktype == int32(qType) {
b.Release()
errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain)
if h.nonIPQuery == "reject" { if h.nonIPQuery == "reject" {
err := h.rejectNonIPQuery(id, qType, domain, writer) go h.rejectNonIPQuery(id, qType, domain, writer)
if err != nil {
return err
}
} }
errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain)
return nil return nil
} }
} }
} }
if isIPQuery { if isIPQuery {
b.Release() go h.handleIPQuery(id, qType, domain, writer)
go h.handleIPQuery(id, qType, domain, writer, timer)
continue
} }
if h.nonIPQuery == "drop" { if isIPQuery || h.nonIPQuery == "drop" {
b.Release() b.Release()
continue continue
} }
if h.nonIPQuery == "reject" { if h.nonIPQuery == "reject" {
go h.rejectNonIPQuery(id, qType, domain, writer)
b.Release() b.Release()
err := h.rejectNonIPQuery(id, qType, domain, writer)
if err != nil {
return err
}
continue continue
} }
} }
@@ -233,7 +219,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
} }
response := func() error { response := func() error {
defer timer.SetTimeout(0)
for { for {
b, err := connReader.ReadMessage() b, err := connReader.ReadMessage()
if err == io.EOF { if err == io.EOF {
@@ -259,7 +244,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
return nil return nil
} }
func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter, timer *signal.ActivityTimer) { func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) {
var ips []net.IP var ips []net.IP
var err error var err error
@@ -334,21 +319,16 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
if err != nil { if err != nil {
errors.LogInfoInner(context.Background(), err, "pack message") errors.LogInfoInner(context.Background(), err, "pack message")
b.Release() b.Release()
timer.SetTimeout(0) return
} }
b.Resize(0, int32(len(msgBytes))) b.Resize(0, int32(len(msgBytes)))
if err := writer.WriteMessage(b); err != nil { if err := writer.WriteMessage(b); err != nil {
errors.LogInfoInner(context.Background(), err, "write IP answer") errors.LogInfoInner(context.Background(), err, "write IP answer")
timer.SetTimeout(0)
} }
} }
func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) error { func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) {
domainT := strings.TrimSuffix(domain, ".")
if domainT == "" {
return errors.New("empty domain name")
}
b := buf.New() b := buf.New()
rawBytes := b.Extend(buf.Size) rawBytes := b.Extend(buf.Size)
builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{ builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{
@@ -369,22 +349,20 @@ func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain stri
if err != nil { if err != nil {
errors.LogInfo(context.Background(), "unexpected domain ", domain, " when building reject message: ", err) errors.LogInfo(context.Background(), "unexpected domain ", domain, " when building reject message: ", err)
b.Release() b.Release()
return err return
} }
msgBytes, err := builder.Finish() msgBytes, err := builder.Finish()
if err != nil { if err != nil {
errors.LogInfoInner(context.Background(), err, "pack reject message") errors.LogInfoInner(context.Background(), err, "pack reject message")
b.Release() b.Release()
return err return
} }
b.Resize(0, int32(len(msgBytes))) b.Resize(0, int32(len(msgBytes)))
if err := writer.WriteMessage(b); err != nil { if err := writer.WriteMessage(b); err != nil {
errors.LogInfoInner(context.Background(), err, "write reject answer") errors.LogInfoInner(context.Background(), err, "write reject answer")
return err
} }
return nil
} }
type outboundConn struct { type outboundConn struct {
@@ -393,7 +371,6 @@ type outboundConn struct {
conn net.Conn conn net.Conn
connReady chan struct{} connReady chan struct{}
closed bool
} }
func (c *outboundConn) dial() error { func (c *outboundConn) dial() error {
@@ -408,16 +385,12 @@ func (c *outboundConn) dial() error {
func (c *outboundConn) Write(b []byte) (int, error) { func (c *outboundConn) Write(b []byte) (int, error) {
c.access.Lock() c.access.Lock()
if c.closed {
c.access.Unlock()
return 0, errors.New("outbound connection closed")
}
if c.conn == nil { if c.conn == nil {
if err := c.dial(); err != nil { if err := c.dial(); err != nil {
c.access.Unlock() c.access.Unlock()
errors.LogWarningInner(context.Background(), err, "failed to dial outbound connection") errors.LogWarningInner(context.Background(), err, "failed to dial outbound connection")
return 0, err return len(b), nil
} }
} }
@@ -427,27 +400,24 @@ func (c *outboundConn) Write(b []byte) (int, error) {
} }
func (c *outboundConn) Read(b []byte) (int, error) { func (c *outboundConn) Read(b []byte) (int, error) {
var conn net.Conn
c.access.Lock() c.access.Lock()
if c.closed { conn = c.conn
c.access.Unlock() c.access.Unlock()
return 0, io.EOF
}
if c.conn == nil { if conn == nil {
c.access.Unlock()
_, open := <-c.connReady _, open := <-c.connReady
if !open { if !open {
return 0, io.EOF return 0, io.EOF
} }
return c.conn.Read(b) conn = c.conn
} }
c.access.Unlock()
return c.conn.Read(b) return conn.Read(b)
} }
func (c *outboundConn) Close() error { func (c *outboundConn) Close() error {
c.access.Lock() c.access.Lock()
c.closed = true
close(c.connReady) close(c.connReady)
if c.conn != nil { if c.conn != nil {
c.conn.Close() c.conn.Close()
+99 -13
View File
@@ -2,8 +2,10 @@ package dokodemo
import ( import (
"context" "context"
"runtime"
"strconv" "strconv"
"strings" "strings"
"sync/atomic"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
@@ -12,10 +14,11 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
) )
@@ -141,11 +144,39 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
}) })
errors.LogInfo(ctx, "received request for ", conn.RemoteAddr()) errors.LogInfo(ctx, "received request for ", conn.RemoteAddr())
var reader buf.Reader plcy := d.policy()
if dest.Network == net.Network_TCP { ctx, cancel := context.WithCancel(ctx)
reader = buf.NewReader(conn) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)
} else {
reader = buf.NewPacketReader(conn) if inbound != nil {
inbound.Timer = timer
}
ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return errors.New("failed to dispatch request").Base(err)
}
requestCount := int32(1)
requestDone := func() error {
defer func() {
if atomic.AddInt32(&requestCount, -1) == 0 {
timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
}
}()
var reader buf.Reader
if dest.Network == net.Network_UDP {
reader = buf.NewPacketReader(conn)
} else {
reader = buf.NewReader(conn)
}
if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport request").Base(err)
}
return nil
} }
var writer buf.Writer var writer buf.Writer
@@ -177,17 +208,72 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
return err return err
} }
writer = NewPacketWriter(pConn, &dest, mark, back) writer = NewPacketWriter(pConn, &dest, mark, back)
defer writer.(*PacketWriter).Close() // close fake UDP conns defer func() {
runtime.Gosched()
common.Interrupt(link.Reader) // maybe duplicated
runtime.Gosched()
writer.(*PacketWriter).Close() // close fake UDP conns
}()
/*
sockopt := &internet.SocketConfig{
Tproxy: internet.SocketConfig_TProxy,
}
if dest.Address.Family().IsIP() {
sockopt.BindAddress = dest.Address.IP()
sockopt.BindPort = uint32(dest.Port)
}
if d.sockopt != nil {
sockopt.Mark = d.sockopt.Mark
}
tConn, err := internet.DialSystem(ctx, net.DestinationFromAddr(conn.RemoteAddr()), sockopt)
if err != nil {
return err
}
defer tConn.Close()
writer = &buf.SequentialWriter{Writer: tConn}
tReader := buf.NewPacketReader(tConn)
requestCount++
tproxyRequest = func() error {
defer func() {
if atomic.AddInt32(&requestCount, -1) == 0 {
timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
}
}()
if err := buf.Copy(tReader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport request (TPROXY conn)").Base(err)
}
return nil
}
*/
} }
} }
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{ responseDone := func() error {
Reader: reader, defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
Writer: writer},
); err != nil { if network == net.Network_UDP && destinationOverridden {
return errors.New("failed to dispatch request").Base(err) buf.Copy(link.Reader, writer) // respect upload's timeout
return nil
}
if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport response").Base(err)
}
return nil
} }
return nil // Unlike Dispatch(), DispatchLink() will not return until the outbound finishes Process()
if err := task.Run(ctx,
task.OnSuccess(func() error { return task.Run(ctx, requestDone) }, task.Close(link.Writer)),
responseDone); err != nil {
runtime.Gosched()
common.Interrupt(link.Writer)
runtime.Gosched()
common.Interrupt(link.Reader)
return errors.New("connection ends").Base(err)
}
return nil
} }
func NewPacketWriter(conn net.PacketConn, d *net.Destination, mark int, back *net.UDPAddr) buf.Writer { func NewPacketWriter(conn net.PacketConn, d *net.Destination, mark int, back *net.UDPAddr) buf.Writer {
+24 -5
View File
@@ -26,6 +26,7 @@ import (
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
) )
var useSplice bool var useSplice bool
@@ -72,7 +73,7 @@ func isValidAddress(addr *net.IPOrDomain) bool {
} }
a := addr.AsAddress() a := addr.AsAddress()
return a != net.AnyIP && a != net.AnyIPv6 return a != net.AnyIP
} }
// Process implements proxy.Outbound. // Process implements proxy.Outbound.
@@ -211,14 +212,16 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
responseDone := func() error { responseDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
if destination.Network == net.Network_TCP && useSplice && proxy.IsRAWTransportWithoutSecurity(conn) { // it would be tls conn in special use case of MITM, we need to let link handle traffic if destination.Network == net.Network_TCP {
var writeConn net.Conn var writeConn net.Conn
var inTimer *signal.ActivityTimer var inTimer *signal.ActivityTimer
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil { if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil && useSplice {
writeConn = inbound.Conn writeConn = inbound.Conn
inTimer = inbound.Timer inTimer = inbound.Timer
} }
return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer, inTimer) if !isTLSConn(conn) { // it would be tls conn in special use case of MITM, we need to let link handle traffic
return proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer, inTimer)
}
} }
var reader buf.Reader var reader buf.Reader
if destination.Network == net.Network_TCP { if destination.Network == net.Network_TCP {
@@ -243,6 +246,22 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return nil return nil
} }
func isTLSConn(conn stat.Connection) bool {
if conn != nil {
statConn, ok := conn.(*stat.CounterConnection)
if ok {
conn = statConn.Connection
}
if _, ok := conn.(*tls.Conn); ok {
return true
}
if _, ok := conn.(*tls.UConn); ok {
return true
}
}
return false
}
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination) buf.Reader { func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
iConn := conn iConn := conn
statConn, ok := iConn.(*stat.CounterConnection) statConn, ok := iConn.(*stat.CounterConnection)
@@ -399,7 +418,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
} }
} }
} }
destAddr := b.UDP.RawNetAddr() destAddr, _ := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
if destAddr == nil { if destAddr == nil {
b.Release() b.Release()
continue continue
+44 -17
View File
@@ -18,12 +18,11 @@ import (
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
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/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
) )
@@ -96,9 +95,6 @@ func (s *Server) ProcessWithFirstbyte(ctx context.Context, network net.Network,
inbound.User = &protocol.MemoryUser{ inbound.User = &protocol.MemoryUser{
Level: s.config.UserLevel, Level: s.config.UserLevel,
} }
if !proxy.IsRAWTransportWithoutSecurity(conn) {
inbound.CanSpliceCopy = 3
}
var reader *bufio.Reader var reader *bufio.Reader
if len(firstbyte) > 0 { if len(firstbyte) > 0 {
readerWithoutFirstbyte := bufio.NewReaderSize(readerOnly{conn}, buf.Size) readerWithoutFirstbyte := bufio.NewReaderSize(readerOnly{conn}, buf.Size)
@@ -173,31 +169,62 @@ Start:
return err return err
} }
func (s *Server) handleConnect(ctx context.Context, _ *http.Request, buffer *bufio.Reader, conn stat.Connection, dest net.Destination, dispatcher routing.Dispatcher, inbound *session.Inbound) error { func (s *Server) handleConnect(ctx context.Context, _ *http.Request, reader *bufio.Reader, conn stat.Connection, dest net.Destination, dispatcher routing.Dispatcher, inbound *session.Inbound) error {
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
if err != nil { if err != nil {
return errors.New("failed to write back OK response").Base(err) return errors.New("failed to write back OK response").Base(err)
} }
reader := buf.NewReader(conn) plcy := s.policy()
if buffer.Buffered() > 0 { ctx, cancel := context.WithCancel(ctx)
payload, err := buf.ReadFrom(io.LimitReader(buffer, int64(buffer.Buffered()))) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)
if inbound != nil {
inbound.Timer = timer
}
ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return err
}
if reader.Buffered() > 0 {
payload, err := buf.ReadFrom(io.LimitReader(reader, int64(reader.Buffered())))
if err != nil { if err != nil {
return err return err
} }
reader = &buf.BufferedReader{Reader: reader, Buffer: payload} if err := link.Writer.WriteMultiBuffer(payload); err != nil {
buffer = nil return err
}
reader = nil
} }
if inbound.CanSpliceCopy == 2 { requestDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
}
responseDone := func() error {
inbound.CanSpliceCopy = 1 inbound.CanSpliceCopy = 1
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
v2writer := buf.NewWriter(conn)
if err := buf.Copy(link.Reader, v2writer, buf.UpdateActivity(timer)); err != nil {
return err
}
return nil
} }
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: reader, closeWriter := task.OnSuccess(requestDone, task.Close(link.Writer))
Writer: buf.NewWriter(conn)}, if err := task.Run(ctx, closeWriter, responseDone); err != nil {
); err != nil { common.Interrupt(link.Reader)
return errors.New("failed to dispatch request").Base(err) common.Interrupt(link.Writer)
return errors.New("connection ends").Base(err)
} }
return nil return nil
} }
+67 -164
View File
@@ -25,7 +25,6 @@ import (
"github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"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"
@@ -177,108 +176,62 @@ type VisionReader struct {
trafficState *TrafficState trafficState *TrafficState
ctx context.Context ctx context.Context
isUplink bool isUplink bool
conn net.Conn
input *bytes.Reader
rawInput *bytes.Buffer
ob *session.Outbound
// internal
directReadCounter stats.Counter
} }
func NewVisionReader(reader buf.Reader, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, ob *session.Outbound) *VisionReader { func NewVisionReader(reader buf.Reader, state *TrafficState, isUplink bool, context context.Context) *VisionReader {
return &VisionReader{ return &VisionReader{
Reader: reader, Reader: reader,
trafficState: trafficState, trafficState: state,
ctx: ctx, ctx: context,
isUplink: isUplink, isUplink: isUplink,
conn: conn,
input: input,
rawInput: rawInput,
ob: ob,
} }
} }
func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
buffer, err := w.Reader.ReadMultiBuffer() buffer, err := w.Reader.ReadMultiBuffer()
if buffer.IsEmpty() { if !buffer.IsEmpty() {
return buffer, err var withinPaddingBuffers *bool
} var remainingContent *int32
var remainingPadding *int32
var withinPaddingBuffers *bool var currentCommand *int
var remainingContent *int32 var switchToDirectCopy *bool
var remainingPadding *int32 if w.isUplink {
var currentCommand *int withinPaddingBuffers = &w.trafficState.Inbound.WithinPaddingBuffers
var switchToDirectCopy *bool remainingContent = &w.trafficState.Inbound.RemainingContent
if w.isUplink { remainingPadding = &w.trafficState.Inbound.RemainingPadding
withinPaddingBuffers = &w.trafficState.Inbound.WithinPaddingBuffers currentCommand = &w.trafficState.Inbound.CurrentCommand
remainingContent = &w.trafficState.Inbound.RemainingContent switchToDirectCopy = &w.trafficState.Inbound.UplinkReaderDirectCopy
remainingPadding = &w.trafficState.Inbound.RemainingPadding
currentCommand = &w.trafficState.Inbound.CurrentCommand
switchToDirectCopy = &w.trafficState.Inbound.UplinkReaderDirectCopy
} else {
withinPaddingBuffers = &w.trafficState.Outbound.WithinPaddingBuffers
remainingContent = &w.trafficState.Outbound.RemainingContent
remainingPadding = &w.trafficState.Outbound.RemainingPadding
currentCommand = &w.trafficState.Outbound.CurrentCommand
switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy
}
if *switchToDirectCopy {
if w.directReadCounter != nil {
w.directReadCounter.Add(int64(buffer.Len()))
}
return buffer, err
}
if *withinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 {
mb2 := make(buf.MultiBuffer, 0, len(buffer))
for _, b := range buffer {
newbuffer := XtlsUnpadding(b, w.trafficState, w.isUplink, w.ctx)
if newbuffer.Len() > 0 {
mb2 = append(mb2, newbuffer)
}
}
buffer = mb2
if *remainingContent > 0 || *remainingPadding > 0 || *currentCommand == 0 {
*withinPaddingBuffers = true
} else if *currentCommand == 1 {
*withinPaddingBuffers = false
} else if *currentCommand == 2 {
*withinPaddingBuffers = false
*switchToDirectCopy = true
} else { } else {
errors.LogInfo(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len()) withinPaddingBuffers = &w.trafficState.Outbound.WithinPaddingBuffers
remainingContent = &w.trafficState.Outbound.RemainingContent
remainingPadding = &w.trafficState.Outbound.RemainingPadding
currentCommand = &w.trafficState.Outbound.CurrentCommand
switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy
} }
}
if w.trafficState.NumberOfPacketToFilter > 0 {
XtlsFilterTls(buffer, w.trafficState, w.ctx)
}
if *switchToDirectCopy { if *withinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 {
// XTLS Vision processes TLS-like conn's input and rawInput mb2 := make(buf.MultiBuffer, 0, len(buffer))
if inputBuffer, err := buf.ReadFrom(w.input); err == nil && !inputBuffer.IsEmpty() { for _, b := range buffer {
buffer, _ = buf.MergeMulti(buffer, inputBuffer) newbuffer := XtlsUnpadding(b, w.trafficState, w.isUplink, w.ctx)
} if newbuffer.Len() > 0 {
if rawInputBuffer, err := buf.ReadFrom(w.rawInput); err == nil && !rawInputBuffer.IsEmpty() { mb2 = append(mb2, newbuffer)
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer) }
}
*w.input = bytes.Reader{} // release memory
w.input = nil
*w.rawInput = bytes.Buffer{} // release memory
w.rawInput = nil
if inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil {
if w.isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
} }
if !w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can have more than one ob buffer = mb2
w.ob.CanSpliceCopy = 1 if *remainingContent > 0 || *remainingPadding > 0 || *currentCommand == 0 {
*withinPaddingBuffers = true
} else if *currentCommand == 1 {
*withinPaddingBuffers = false
} else if *currentCommand == 2 {
*withinPaddingBuffers = false
*switchToDirectCopy = true
} else {
errors.LogInfo(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len())
} }
} }
readerConn, readCounter, _ := UnwrapRawConn(w.conn) if w.trafficState.NumberOfPacketToFilter > 0 {
w.directReadCounter = readCounter XtlsFilterTls(buffer, w.trafficState, w.ctx)
w.Reader = buf.NewReader(readerConn) }
} }
return buffer, err return buffer, err
} }
@@ -287,32 +240,28 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
// Note Vision probably only make sense as the inner most layer of writer, since it need assess traffic state from origin proxy traffic // Note Vision probably only make sense as the inner most layer of writer, since it need assess traffic state from origin proxy traffic
type VisionWriter struct { type VisionWriter struct {
buf.Writer buf.Writer
trafficState *TrafficState trafficState *TrafficState
ctx context.Context ctx context.Context
isUplink bool writeOnceUserUUID []byte
conn net.Conn isUplink bool
ob *session.Outbound
// internal
writeOnceUserUUID []byte
directWriteCounter stats.Counter
} }
func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound) *VisionWriter { func NewVisionWriter(writer buf.Writer, state *TrafficState, isUplink bool, context context.Context) *VisionWriter {
w := make([]byte, len(trafficState.UserUUID)) w := make([]byte, len(state.UserUUID))
copy(w, trafficState.UserUUID) copy(w, state.UserUUID)
return &VisionWriter{ return &VisionWriter{
Writer: writer, Writer: writer,
trafficState: trafficState, trafficState: state,
ctx: ctx, ctx: context,
writeOnceUserUUID: w, writeOnceUserUUID: w,
isUplink: isUplink, isUplink: isUplink,
conn: conn,
ob: ob,
} }
} }
func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if w.trafficState.NumberOfPacketToFilter > 0 {
XtlsFilterTls(mb, w.trafficState, w.ctx)
}
var isPadding *bool var isPadding *bool
var switchToDirectCopy *bool var switchToDirectCopy *bool
if w.isUplink { if w.isUplink {
@@ -322,29 +271,6 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
isPadding = &w.trafficState.Inbound.IsPadding isPadding = &w.trafficState.Inbound.IsPadding
switchToDirectCopy = &w.trafficState.Inbound.DownlinkWriterDirectCopy switchToDirectCopy = &w.trafficState.Inbound.DownlinkWriterDirectCopy
} }
if *switchToDirectCopy {
if inbound := session.InboundFromContext(w.ctx); inbound != nil {
if !w.isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 {
w.ob.CanSpliceCopy = 1
}
}
rawConn, _, writerCounter := UnwrapRawConn(w.conn)
w.Writer = buf.NewWriter(rawConn)
w.directWriteCounter = writerCounter
*switchToDirectCopy = false
}
if !mb.IsEmpty() && w.directWriteCounter != nil {
w.directWriteCounter.Add(int64(mb.Len()))
}
if w.trafficState.NumberOfPacketToFilter > 0 {
XtlsFilterTls(mb, w.trafficState, w.ctx)
}
if *isPadding { if *isPadding {
if len(mb) == 1 && mb[0] == nil { if len(mb) == 1 && mb[0] == nil {
mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header
@@ -598,33 +524,24 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
} }
} }
// UnwrapRawConn support unwrap encryption, stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it // UnwrapRawConn support unwrap stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
var readCounter, writerCounter stats.Counter var readCounter, writerCounter stats.Counter
if conn != nil { if conn != nil {
isEncryption := false statConn, ok := conn.(*stat.CounterConnection)
if commonConn, ok := conn.(*encryption.CommonConn); ok { if ok {
conn = commonConn.Conn
isEncryption = true
}
if xorConn, ok := conn.(*encryption.XorConn); ok {
return xorConn, nil, nil // full-random xorConn should not be penetrated
}
if statConn, ok := conn.(*stat.CounterConnection); ok {
conn = statConn.Connection conn = statConn.Connection
readCounter = statConn.ReadCounter readCounter = statConn.ReadCounter
writerCounter = statConn.WriteCounter writerCounter = statConn.WriteCounter
} }
if !isEncryption { // avoids double penetration if xc, ok := conn.(*tls.Conn); ok {
if xc, ok := conn.(*tls.Conn); ok { conn = xc.NetConn()
conn = xc.NetConn() } else if utlsConn, ok := conn.(*tls.UConn); ok {
} else if utlsConn, ok := conn.(*tls.UConn); ok { conn = utlsConn.NetConn()
conn = utlsConn.NetConn() } else if realityConn, ok := conn.(*reality.Conn); ok {
} else if realityConn, ok := conn.(*reality.Conn); ok { conn = realityConn.NetConn()
conn = realityConn.NetConn() } else if realityUConn, ok := conn.(*reality.UConn); ok {
} else if realityUConn, ok := conn.(*reality.UConn); ok { conn = realityUConn.NetConn()
conn = realityUConn.NetConn()
}
} }
if pc, ok := conn.(*proxyproto.Conn); ok { if pc, ok := conn.(*proxyproto.Conn); ok {
conn = pc.Raw() conn = pc.Raw()
@@ -678,10 +595,10 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
errors.LogInfo(ctx, "CopyRawConn splice") errors.LogInfo(ctx, "CopyRawConn splice")
statWriter, _ := writer.(*dispatcher.SizeStatWriter) statWriter, _ := writer.(*dispatcher.SizeStatWriter)
//runtime.Gosched() // necessary //runtime.Gosched() // necessary
time.Sleep(time.Millisecond) // without this, there will be a rare ssl error for freedom splice time.Sleep(time.Millisecond) // without this, there will be a rare ssl error for freedom splice
timer.SetTimeout(8 * time.Hour) // prevent leak, just in case timer.SetTimeoutIfNotOverridden(24 * time.Hour) // prevent leak, just in case
if inTimer != nil { if inTimer != nil {
inTimer.SetTimeout(8 * time.Hour) inTimer.SetTimeoutIfNotOverridden(24 * time.Hour)
} }
w, err := tc.ReadFrom(readerConn) w, err := tc.ReadFrom(readerConn)
if readCounter != nil { if readCounter != nil {
@@ -709,29 +626,15 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
} }
} }
if err != nil { if err != nil {
if errors.Cause(err) == io.EOF {
return nil
}
return err return err
} }
} }
} }
func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error { func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {
errors.LogInfo(ctx, "CopyRawConn (maybe) readv") errors.LogInfo(ctx, "CopyRawConn readv")
if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil { if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
return errors.New("failed to process response").Base(err) return errors.New("failed to process response").Base(err)
} }
return nil return nil
} }
func IsRAWTransportWithoutSecurity(conn stat.Connection) bool {
iConn := conn
if statConn, ok := iConn.(*stat.CounterConnection); ok {
iConn = statConn.Connection
}
_, ok1 := iConn.(*proxyproto.Conn)
_, ok2 := iConn.(*net.TCPConn)
_, ok3 := iConn.(*internet.UnixConnWrapper)
return ok1 || ok2 || ok3
}
+3 -3
View File
@@ -104,12 +104,12 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error { func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {
udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) { udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
request := protocol.RequestHeaderFromContext(ctx) request := protocol.RequestHeaderFromContext(ctx)
payload := packet.Payload
if request == nil { if request == nil {
payload.Release()
return return
} }
payload := packet.Payload
if payload.UDP != nil { if payload.UDP != nil {
request = &protocol.RequestHeader{ request = &protocol.RequestHeader{
User: request.User, User: request.User,
@@ -124,9 +124,9 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
errors.LogWarningInner(ctx, err, "failed to encode UDP packet") errors.LogWarningInner(ctx, err, "failed to encode UDP packet")
return return
} }
defer data.Release()
conn.Write(data.Bytes()) conn.Write(data.Bytes())
data.Release()
}) })
defer udpServer.RemoveRay() defer udpServer.RemoveRay()
+52 -17
View File
@@ -14,12 +14,12 @@ import (
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
udp_proto "github.com/xtls/xray-core/common/protocol/udp" udp_proto "github.com/xtls/xray-core/common/protocol/udp"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/http" "github.com/xtls/xray-core/proxy/http"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/udp" "github.com/xtls/xray-core/transport/internet/udp"
) )
@@ -75,9 +75,6 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
inbound.User = &protocol.MemoryUser{ inbound.User = &protocol.MemoryUser{
Level: s.config.UserLevel, Level: s.config.UserLevel,
} }
if !proxy.IsRAWTransportWithoutSecurity(conn) {
inbound.CanSpliceCopy = 3
}
switch network { switch network {
case net.Network_TCP: case net.Network_TCP:
@@ -157,16 +154,8 @@ func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatche
Reason: "", Reason: "",
}) })
} }
if inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1 return s.transport(ctx, reader, conn, dest, dispatcher, inbound)
}
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: reader,
Writer: buf.NewWriter(conn)},
); err != nil {
return errors.New("failed to dispatch request").Base(err)
}
return nil
} }
if request.Command == protocol.RequestCommandUDP { if request.Command == protocol.RequestCommandUDP {
@@ -185,6 +174,52 @@ func (*Server) handleUDP(c io.Reader) error {
return common.Error2(io.Copy(buf.DiscardBytes, c)) return common.Error2(io.Copy(buf.DiscardBytes, c))
} }
func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writer, dest net.Destination, dispatcher routing.Dispatcher, inbound *session.Inbound) error {
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, s.policy().Timeouts.ConnectionIdle)
if inbound != nil {
inbound.Timer = timer
}
plcy := s.policy()
ctx = policy.ContextWithBufferPolicy(ctx, plcy.Buffer)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return err
}
requestDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
if err := buf.Copy(buf.NewReader(reader), link.Writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport all TCP request").Base(err)
}
return nil
}
responseDone := func() error {
inbound.CanSpliceCopy = 1
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
v2writer := buf.NewWriter(writer)
if err := buf.Copy(link.Reader, v2writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport all TCP response").Base(err)
}
return nil
}
requestDonePost := task.OnSuccess(requestDone, task.Close(link.Writer))
if err := task.Run(ctx, requestDonePost, responseDone); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
return errors.New("connection ends").Base(err)
}
return nil
}
func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error { func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {
if s.udpFilter != nil && !s.udpFilter.Check(conn.RemoteAddr()) { if s.udpFilter != nil && !s.udpFilter.Check(conn.RemoteAddr()) {
errors.LogDebug(ctx, "Unauthorized UDP access from ", conn.RemoteAddr().String()) errors.LogDebug(ctx, "Unauthorized UDP access from ", conn.RemoteAddr().String())
@@ -196,7 +231,6 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
request := protocol.RequestHeaderFromContext(ctx) request := protocol.RequestHeaderFromContext(ctx)
if request == nil { if request == nil {
payload.Release()
return return
} }
@@ -215,9 +249,9 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
errors.LogWarningInner(ctx, err, "failed to write UDP response") errors.LogWarningInner(ctx, err, "failed to write UDP response")
return return
} }
defer udpMessage.Release()
conn.Write(udpMessage.Bytes()) conn.Write(udpMessage.Bytes())
udpMessage.Release()
}) })
defer udpServer.RemoveRay() defer udpServer.RemoveRay()
@@ -225,6 +259,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
if inbound != nil && inbound.Source.IsValid() { if inbound != nil && inbound.Source.IsValid() {
errors.LogInfo(ctx, "client UDP connection from ", inbound.Source) errors.LogInfo(ctx, "client UDP connection from ", inbound.Source)
} }
inbound.CanSpliceCopy = 1
var dest *net.Destination var dest *net.Destination
-2
View File
@@ -113,11 +113,9 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
target = b.UDP target = b.UDP
} }
if _, err := w.writePacket(b.Bytes(), *target); err != nil { if _, err := w.writePacket(b.Bytes(), *target); err != nil {
b.Release()
buf.ReleaseMulti(mb) buf.ReleaseMulti(mb)
return err return err
} }
b.Release()
} }
return nil return nil
} }
+35 -51
View File
@@ -233,7 +233,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
sessionPolicy = s.policyManager.ForLevel(user.Level) sessionPolicy = s.policyManager.ForLevel(user.Level)
if destination.Network == net.Network_UDP { // handle udp request if destination.Network == net.Network_UDP { // handle udp request
return s.handleUDPPayload(ctx, sessionPolicy, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher) return s.handleUDPPayload(ctx, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher)
} }
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
@@ -248,11 +248,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher) return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher)
} }
func (s *Server) handleUDPPayload(ctx context.Context, sessionPolicy policy.Session, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error { func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
defer timer.SetTimeout(0)
udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) { udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
udpPayload := packet.Payload udpPayload := packet.Payload
if udpPayload.UDP == nil { if udpPayload.UDP == nil {
@@ -261,9 +257,6 @@ func (s *Server) handleUDPPayload(ctx context.Context, sessionPolicy policy.Sess
if err := clientWriter.WriteMultiBuffer(buf.MultiBuffer{udpPayload}); err != nil { if err := clientWriter.WriteMultiBuffer(buf.MultiBuffer{udpPayload}); err != nil {
errors.LogWarningInner(ctx, err, "failed to write response") errors.LogWarningInner(ctx, err, "failed to write response")
cancel()
} else {
timer.Update()
} }
}) })
defer udpServer.RemoveRay() defer udpServer.RemoveRay()
@@ -273,56 +266,47 @@ func (s *Server) handleUDPPayload(ctx context.Context, sessionPolicy policy.Sess
var dest *net.Destination var dest *net.Destination
requestDone := func() error { for {
for { select {
select { case <-ctx.Done():
case <-ctx.Done(): return nil
default:
mb, err := clientReader.ReadMultiBuffer()
if err != nil {
if errors.Cause(err) != io.EOF {
return errors.New("unexpected EOF").Base(err)
}
return nil return nil
default: }
mb, err := clientReader.ReadMultiBuffer()
if err != nil {
if errors.Cause(err) != io.EOF {
return errors.New("unexpected EOF").Base(err)
}
return nil
}
mb2, b := buf.SplitFirst(mb) mb2, b := buf.SplitFirst(mb)
if b == nil { if b == nil {
continue continue
} }
timer.Update() destination := *b.UDP
destination := *b.UDP
currentPacketCtx := ctx currentPacketCtx := ctx
if inbound.Source.IsValid() { if inbound.Source.IsValid() {
currentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ currentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: inbound.Source, From: inbound.Source,
To: destination, To: destination,
Status: log.AccessAccepted, Status: log.AccessAccepted,
Reason: "", Reason: "",
Email: user.Email, Email: user.Email,
}) })
} }
errors.LogInfo(ctx, "tunnelling request to ", destination) errors.LogInfo(ctx, "tunnelling request to ", destination)
if !s.cone || dest == nil { if !s.cone || dest == nil {
dest = &destination dest = &destination
} }
udpServer.Dispatch(currentPacketCtx, *dest, b) // first packet udpServer.Dispatch(currentPacketCtx, *dest, b) // first packet
for _, payload := range mb2 { for _, payload := range mb2 {
udpServer.Dispatch(currentPacketCtx, *dest, payload) udpServer.Dispatch(currentPacketCtx, *dest, payload)
}
} }
} }
} }
if err := task.Run(ctx, requestDone); err != nil {
return err
}
return nil
} }
func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session, func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session,
+1 -10
View File
@@ -18,9 +18,6 @@ func (a *Account) AsAccount() (protocol.Account, error) {
ID: protocol.NewID(id), ID: protocol.NewID(id),
Flow: a.Flow, // needs parser here? Flow: a.Flow, // needs parser here?
Encryption: a.Encryption, // needs parser here? Encryption: a.Encryption, // needs parser here?
XorMode: a.XorMode,
Seconds: a.Seconds,
Padding: a.Padding,
}, nil }, nil
} }
@@ -30,11 +27,8 @@ type MemoryAccount struct {
ID *protocol.ID ID *protocol.ID
// Flow of the account. May be "xtls-rprx-vision". // Flow of the account. May be "xtls-rprx-vision".
Flow string Flow string
// Encryption of the account. Used for client connections, and only accepts "none" for now.
Encryption string Encryption string
XorMode uint32
Seconds uint32
Padding string
} }
// Equals implements protocol.Account.Equals(). // Equals implements protocol.Account.Equals().
@@ -51,8 +45,5 @@ func (a *MemoryAccount) ToProto() proto.Message {
Id: a.ID.String(), Id: a.ID.String(),
Flow: a.Flow, Flow: a.Flow,
Encryption: a.Encryption, Encryption: a.Encryption,
XorMode: a.XorMode,
Seconds: a.Seconds,
Padding: a.Padding,
} }
} }
+14 -42
View File
@@ -28,11 +28,9 @@ type Account struct {
// ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57".
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// Flow settings. May be "xtls-rprx-vision". // Flow settings. May be "xtls-rprx-vision".
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"` Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
// Encryption settings. Only applies to client side, and only accepts "none" for now.
Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"` Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"`
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"`
Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"`
Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"`
} }
func (x *Account) Reset() { func (x *Account) Reset() {
@@ -86,49 +84,23 @@ func (x *Account) GetEncryption() string {
return "" return ""
} }
func (x *Account) GetXorMode() uint32 {
if x != nil {
return x.XorMode
}
return 0
}
func (x *Account) GetSeconds() uint32 {
if x != nil {
return x.Seconds
}
return 0
}
func (x *Account) GetPadding() string {
if x != nil {
return x.Padding
}
return ""
}
var File_proxy_vless_account_proto protoreflect.FileDescriptor var File_proxy_vless_account_proto protoreflect.FileDescriptor
var file_proxy_vless_account_proto_rawDesc = []byte{ var file_proxy_vless_account_proto_rawDesc = []byte{
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63, 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x9b, 0x01, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x4d, 0x0a,
0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77,
0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a,
0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x09, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14,
0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10,
0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73,
0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58,
0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
+1 -4
View File
@@ -11,9 +11,6 @@ message Account {
string id = 1; string id = 1;
// Flow settings. May be "xtls-rprx-vision". // Flow settings. May be "xtls-rprx-vision".
string flow = 2; string flow = 2;
// Encryption settings. Only applies to client side, and only accepts "none" for now.
string encryption = 3; string encryption = 3;
uint32 xorMode = 4;
uint32 seconds = 5;
string padding = 6;
} }
+5 -6
View File
@@ -3,12 +3,10 @@ package encoding
import ( import (
"context" "context"
"io" "io"
"net"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"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/session"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@@ -63,14 +61,15 @@ func DecodeHeaderAddons(buffer *buf.Buffer, reader io.Reader) (*Addons, error) {
} }
// EncodeBodyAddons returns a Writer that auto-encrypt content written by caller. // EncodeBodyAddons returns a Writer that auto-encrypt content written by caller.
func EncodeBodyAddons(writer buf.Writer, request *protocol.RequestHeader, requestAddons *Addons, state *proxy.TrafficState, isUplink bool, context context.Context, conn net.Conn, ob *session.Outbound) buf.Writer { func EncodeBodyAddons(writer io.Writer, request *protocol.RequestHeader, requestAddons *Addons, state *proxy.TrafficState, isUplink bool, context context.Context) buf.Writer {
if request.Command == protocol.RequestCommandUDP { if request.Command == protocol.RequestCommandUDP {
return NewMultiLengthPacketWriter(writer) return NewMultiLengthPacketWriter(writer.(buf.Writer))
} }
w := buf.NewWriter(writer)
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
return proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob) w = proxy.NewVisionWriter(w, state, isUplink, context)
} }
return writer return w
} }
// DecodeBodyAddons returns a Reader from which caller can fetch decrypted body. // DecodeBodyAddons returns a Reader from which caller can fetch decrypted body.
+67 -2
View File
@@ -1,6 +1,7 @@
package encoding package encoding
import ( import (
"bytes"
"context" "context"
"io" "io"
@@ -10,6 +11,7 @@ import (
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless"
) )
@@ -169,8 +171,8 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A
return responseAddons, nil return responseAddons, nil
} }
// XtlsRead can switch to splice copy // XtlsRead filter and read xtls protocol
func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, trafficState *proxy.TrafficState, isUplink bool, ctx context.Context) error { func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
err := func() error { err := func() error {
for { for {
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
@@ -179,11 +181,74 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer,
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil { if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {
writerConn = inbound.Conn writerConn = inbound.Conn
inTimer = inbound.Timer inTimer = inbound.Timer
if isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if !isUplink && ob != nil && ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can change
ob.CanSpliceCopy = 1
}
} }
return proxy.CopyRawConnIfExist(ctx, conn, writerConn, writer, timer, inTimer) return proxy.CopyRawConnIfExist(ctx, conn, writerConn, writer, timer, inTimer)
} }
buffer, err := reader.ReadMultiBuffer() buffer, err := reader.ReadMultiBuffer()
if !buffer.IsEmpty() { if !buffer.IsEmpty() {
timer.Update()
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
// XTLS Vision processes struct TLS Conn's input and rawInput
if inputBuffer, err := buf.ReadFrom(input); err == nil {
if !inputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
}
}
if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil {
if !rawInputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
}
}
}
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
return werr
}
}
if err != nil {
return err
}
}
}()
if err != nil && errors.Cause(err) != io.EOF {
return err
}
return nil
}
// XtlsWrite filter and write xtls protocol
func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, conn net.Conn, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
err := func() error {
var ct stats.Counter
for {
buffer, err := reader.ReadMultiBuffer()
if isUplink && trafficState.Outbound.UplinkWriterDirectCopy || !isUplink && trafficState.Inbound.DownlinkWriterDirectCopy {
if inbound := session.InboundFromContext(ctx); inbound != nil {
if !isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1
}
if isUplink && ob != nil && ob.CanSpliceCopy == 2 {
ob.CanSpliceCopy = 1
}
}
rawConn, _, writerCounter := proxy.UnwrapRawConn(conn)
writer = buf.NewWriter(rawConn)
ct = writerCounter
if isUplink {
trafficState.Outbound.UplinkWriterDirectCopy = false
} else {
trafficState.Inbound.DownlinkWriterDirectCopy = false
}
}
if !buffer.IsEmpty() {
if ct != nil {
ct.Add(int64(buffer.Len()))
}
timer.Update() timer.Update()
if werr := writer.WriteMultiBuffer(buffer); werr != nil { if werr := writer.WriteMultiBuffer(buffer); werr != nil {
return werr return werr
-210
View File
@@ -1,210 +0,0 @@
package encryption
import (
"crypto/cipher"
"crypto/ecdh"
"crypto/mlkem"
"crypto/rand"
"io"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"lukechampine.com/blake3"
)
type ClientInstance struct {
NfsPKeys []any
NfsPKeysBytes [][]byte
Hash32s [][32]byte
RelaysLength int
XorMode uint32
Seconds uint32
PaddingLens [][3]int
PaddingGaps [][3]int
RWLock sync.RWMutex
Expire time.Time
PfsKey []byte
Ticket []byte
}
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
if i.NfsPKeys != nil {
return errors.New("already initialized")
}
l := len(nfsPKeysBytes)
if l == 0 {
return errors.New("empty nfsPKeysBytes")
}
i.NfsPKeys = make([]any, l)
i.NfsPKeysBytes = nfsPKeysBytes
i.Hash32s = make([][32]byte, l)
for j, k := range nfsPKeysBytes {
if len(k) == 32 {
if i.NfsPKeys[j], err = ecdh.X25519().NewPublicKey(k); err != nil {
return
}
i.RelaysLength += 32 + 32
} else {
if i.NfsPKeys[j], err = mlkem.NewEncapsulationKey768(k); err != nil {
return
}
i.RelaysLength += 1088 + 32
}
i.Hash32s[j] = blake3.Sum256(k)
}
i.RelaysLength -= 32
i.XorMode = xorMode
i.Seconds = seconds
return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
}
func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
if i.NfsPKeys == nil {
return nil, errors.New("uninitialized")
}
c := NewCommonConn(conn, protocol.HasAESGCMHardwareSupport)
ivAndRealysLength := 16 + i.RelaysLength
pfsKeyExchangeLength := 18 + 1184 + 32 + 16
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength)
iv := clientHello[:16]
rand.Read(iv)
relays := clientHello[16:ivAndRealysLength]
var nfsKey []byte
var lastCTR cipher.Stream
for j, k := range i.NfsPKeys {
var index = 32
if k, ok := k.(*ecdh.PublicKey); ok {
privateKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
copy(relays, privateKey.PublicKey().Bytes())
var err error
nfsKey, err = privateKey.ECDH(k)
if err != nil {
return nil, err
}
}
if k, ok := k.(*mlkem.EncapsulationKey768); ok {
var ciphertext []byte
nfsKey, ciphertext = k.Encapsulate()
copy(relays, ciphertext)
index = 1088
}
if i.XorMode > 0 { // this xor can (others can't) be recovered by client's config, revealing an X25519 public key / ML-KEM-768 ciphertext, that's why "native" values
NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // make X25519 public key / ML-KEM-768 ciphertext distinguishable from random bytes
}
if lastCTR != nil {
lastCTR.XORKeyStream(relays, relays[:32]) // make this relay irreplaceable
}
if j == len(i.NfsPKeys)-1 {
break
}
lastCTR = NewCTR(nfsKey, iv)
lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:])
relays = relays[index+32:]
}
nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
if i.Seconds > 0 {
i.RWLock.RLock()
if time.Now().Before(i.Expire) {
c.Client = i
c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection
nfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)
nfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)
i.RWLock.RUnlock()
c.PreWrite = clientHello[:ivAndRealysLength+18+32]
c.AEAD = NewAEAD(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey, c.UseAES)
if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16)
}
return c, nil
}
i.RWLock.RUnlock()
}
pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength]
nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
mlkem768DKey, _ := mlkem.GenerateKey768()
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...)
nfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:]
nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0]
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if l > 0 {
if _, err := conn.Write(clientHello[:l]); err != nil {
return nil, err
}
clientHello = clientHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
}
encryptedPfsPublicKey := make([]byte, 1088+32+16)
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
return nil, err
}
nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088])
if err != nil {
return nil, err
}
peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1088 : 1088+32])
if err != nil {
return nil, err
}
x25519Key, err := x25519SKey.ECDH(peerX25519PKey)
if err != nil {
return nil, err
}
pfsKey := make([]byte, 32+32) // no more capacity
copy(pfsKey, mlkem768Key)
copy(pfsKey[32:], x25519Key)
c.UnitedKey = append(pfsKey, nfsKey...)
c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES)
encryptedTicket := make([]byte, 32)
if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
return nil, err
}
if _, err := c.PeerAEAD.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
return nil, err
}
seconds := DecodeLength(encryptedTicket)
if i.Seconds > 0 && seconds > 0 {
i.RWLock.Lock()
i.Expire = time.Now().Add(time.Duration(seconds) * time.Second)
i.PfsKey = pfsKey
i.Ticket = encryptedTicket[:16]
i.RWLock.Unlock()
}
encryptedLength := make([]byte, 18)
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err
}
if _, err := c.PeerAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err
}
length := DecodeLength(encryptedLength[:2])
c.PeerPadding = make([]byte, length) // important: allows server sends padding slowly, eliminating 1-RTT's traffic pattern
if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), NewCTR(c.UnitedKey, encryptedTicket[:16]), 0, length)
}
return c, nil
}
-280
View File
@@ -1,280 +0,0 @@
package encryption
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
"io"
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"golang.org/x/crypto/chacha20poly1305"
"lukechampine.com/blake3"
)
var OutBytesPool = sync.Pool{
New: func() any {
return make([]byte, 5+8192+16)
},
}
type CommonConn struct {
net.Conn
UseAES bool
Client *ClientInstance
UnitedKey []byte
PreWrite []byte
AEAD *AEAD
PeerAEAD *AEAD
PeerPadding []byte
rawInput bytes.Buffer
input bytes.Reader
}
func NewCommonConn(conn net.Conn, useAES bool) *CommonConn {
return &CommonConn{
Conn: conn,
UseAES: useAES,
}
}
func (c *CommonConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
outBytes := OutBytesPool.Get().([]byte)
defer OutBytesPool.Put(outBytes)
for n := 0; n < len(b); {
b := b[n:]
if len(b) > 8192 {
b = b[:8192] // for avoiding another copy() in peer's Read()
}
n += len(b)
headerAndData := outBytes[:5+len(b)+16]
EncodeHeader(headerAndData, len(b)+16)
max := false
if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) {
max = true
}
c.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5])
if max {
c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES)
}
if c.PreWrite != nil {
headerAndData = append(c.PreWrite, headerAndData...)
c.PreWrite = nil
}
if _, err := c.Conn.Write(headerAndData); err != nil {
return 0, err
}
}
return len(b), nil
}
func (c *CommonConn) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
if c.PeerAEAD == nil { // client's 0-RTT
serverRandom := make([]byte, 16)
if _, err := io.ReadFull(c.Conn, serverRandom); err != nil {
return 0, err
}
c.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES)
if xorConn, ok := c.Conn.(*XorConn); ok {
xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom)
}
}
if c.PeerPadding != nil { // client's 1-RTT
if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil {
return 0, err
}
if _, err := c.PeerAEAD.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {
return 0, err
}
c.PeerPadding = nil
}
if c.input.Len() > 0 {
return c.input.Read(b)
}
peerHeader := [5]byte{}
if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil {
return 0, err
}
l, err := DecodeHeader(peerHeader[:]) // l: 17~17000
if err != nil {
if c.Client != nil && strings.Contains(err.Error(), "invalid header: ") { // client's 0-RTT
c.Client.RWLock.Lock()
if bytes.HasPrefix(c.UnitedKey, c.Client.PfsKey) {
c.Client.Expire = time.Now() // expired
}
c.Client.RWLock.Unlock()
return 0, errors.New("new handshake needed")
}
return 0, err
}
c.Client = nil
if c.rawInput.Cap() < l {
c.rawInput.Grow(l) // no need to use sync.Pool, because we are always reading
}
peerData := c.rawInput.Bytes()[:l]
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
return 0, err
}
dst := peerData[:l-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // avoids another copy()
}
var newAEAD *AEAD
if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) {
newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES)
}
_, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:])
if newAEAD != nil {
c.PeerAEAD = newAEAD
}
if err != nil {
return 0, err
}
if len(dst) > len(b) {
c.input.Reset(dst[copy(b, dst):])
dst = b // for len(dst)
}
return len(dst), nil
}
type AEAD struct {
cipher.AEAD
Nonce [12]byte
}
func NewAEAD(ctx, key []byte, useAES bool) *AEAD {
k := make([]byte, 32)
blake3.DeriveKey(k, string(ctx), key)
var aead cipher.AEAD
if useAES {
block, _ := aes.NewCipher(k)
aead, _ = cipher.NewGCM(block)
} else {
aead, _ = chacha20poly1305.New(k)
}
return &AEAD{AEAD: aead}
}
func (a *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if nonce == nil {
nonce = IncreaseNonce(a.Nonce[:])
}
return a.AEAD.Seal(dst, nonce, plaintext, additionalData)
}
func (a *AEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if nonce == nil {
nonce = IncreaseNonce(a.Nonce[:])
}
return a.AEAD.Open(dst, nonce, ciphertext, additionalData)
}
func IncreaseNonce(nonce []byte) []byte {
for i := range 12 {
nonce[11-i]++
if nonce[11-i] != 0 {
break
}
}
return nonce
}
var MaxNonce = bytes.Repeat([]byte{255}, 12)
func EncodeLength(l int) []byte {
return []byte{byte(l >> 8), byte(l)}
}
func DecodeLength(b []byte) int {
return int(b[0])<<8 | int(b[1])
}
func EncodeHeader(h []byte, l int) {
h[0] = 23
h[1] = 3
h[2] = 3
h[3] = byte(l >> 8)
h[4] = byte(l)
}
func DecodeHeader(h []byte) (l int, err error) {
l = int(h[3])<<8 | int(h[4])
if h[0] != 23 || h[1] != 3 || h[2] != 3 {
l = 0
}
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
err = errors.New("invalid header: " + fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read()
}
return
}
func ParsePadding(padding string, paddingLens, paddingGaps *[][3]int) (err error) {
if padding == "" {
return
}
maxLen := 0
for i, s := range strings.Split(padding, ".") {
x := strings.Split(s, "-")
if len(x) < 3 || x[0] == "" || x[1] == "" || x[2] == "" {
return errors.New("invalid padding lenth/gap parameter: " + s)
}
y := [3]int{}
if y[0], err = strconv.Atoi(x[0]); err != nil {
return
}
if y[1], err = strconv.Atoi(x[1]); err != nil {
return
}
if y[2], err = strconv.Atoi(x[2]); err != nil {
return
}
if i == 0 && (y[0] < 100 || y[1] < 18+17 || y[2] < 18+17) {
return errors.New("first padding length must not be smaller than 35")
}
if i%2 == 0 {
*paddingLens = append(*paddingLens, y)
maxLen += max(y[1], y[2])
} else {
*paddingGaps = append(*paddingGaps, y)
}
}
if maxLen > 18+65535 {
return errors.New("total padding length must not be larger than 65553")
}
return
}
func CreatPadding(paddingLens, paddingGaps [][3]int) (length int, lens []int, gaps []time.Duration) {
if len(paddingLens) == 0 {
paddingLens = [][3]int{{100, 111, 1111}, {50, 0, 3333}}
paddingGaps = [][3]int{{75, 0, 111}}
}
for _, y := range paddingLens {
l := 0
if y[0] >= int(crypto.RandBetween(0, 100)) {
l = int(crypto.RandBetween(int64(y[1]), int64(y[2])))
}
lens = append(lens, l)
length += l
}
for _, y := range paddingGaps {
g := 0
if y[0] >= int(crypto.RandBetween(0, 100)) {
g = int(crypto.RandBetween(int64(y[1]), int64(y[2])))
}
gaps = append(gaps, time.Duration(g)*time.Millisecond)
}
return
}
-328
View File
@@ -1,328 +0,0 @@
package encryption
import (
"bytes"
"crypto/cipher"
"crypto/ecdh"
"crypto/mlkem"
"crypto/rand"
"fmt"
"io"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"lukechampine.com/blake3"
)
type ServerSession struct {
PfsKey []byte
NfsKeys sync.Map
}
type ServerInstance struct {
NfsSKeys []any
NfsPKeysBytes [][]byte
Hash32s [][32]byte
RelaysLength int
XorMode uint32
SecondsFrom int64
SecondsTo int64
PaddingLens [][3]int
PaddingGaps [][3]int
RWLock sync.RWMutex
Closed bool
Lasts map[int64][16]byte
Tickets [][16]byte
Sessions map[[16]byte]*ServerSession
}
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode uint32, secondsFrom, secondsTo int64, padding string) (err error) {
if i.NfsSKeys != nil {
return errors.New("already initialized")
}
l := len(nfsSKeysBytes)
if l == 0 {
return errors.New("empty nfsSKeysBytes")
}
i.NfsSKeys = make([]any, l)
i.NfsPKeysBytes = make([][]byte, l)
i.Hash32s = make([][32]byte, l)
for j, k := range nfsSKeysBytes {
if len(k) == 32 {
if i.NfsSKeys[j], err = ecdh.X25519().NewPrivateKey(k); err != nil {
return
}
i.NfsPKeysBytes[j] = i.NfsSKeys[j].(*ecdh.PrivateKey).PublicKey().Bytes()
i.RelaysLength += 32 + 32
} else {
if i.NfsSKeys[j], err = mlkem.NewDecapsulationKey768(k); err != nil {
return
}
i.NfsPKeysBytes[j] = i.NfsSKeys[j].(*mlkem.DecapsulationKey768).EncapsulationKey().Bytes()
i.RelaysLength += 1088 + 32
}
i.Hash32s[j] = blake3.Sum256(i.NfsPKeysBytes[j])
}
i.RelaysLength -= 32
i.XorMode = xorMode
i.SecondsFrom = secondsFrom
i.SecondsTo = secondsTo
err = ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
if err != nil {
return
}
if i.SecondsFrom > 0 || i.SecondsTo > 0 {
i.Lasts = make(map[int64][16]byte)
i.Tickets = make([][16]byte, 0, 1024)
i.Sessions = make(map[[16]byte]*ServerSession)
go func() {
for {
time.Sleep(time.Minute)
i.RWLock.Lock()
if i.Closed {
i.RWLock.Unlock()
return
}
minute := time.Now().Unix() / 60
last := i.Lasts[minute]
delete(i.Lasts, minute)
delete(i.Lasts, minute-1) // for insurance
if last != [16]byte{} {
for j, ticket := range i.Tickets {
delete(i.Sessions, ticket)
if ticket == last {
i.Tickets = i.Tickets[j+1:]
break
}
}
}
i.RWLock.Unlock()
}
}()
}
return
}
func (i *ServerInstance) Close() (err error) {
i.RWLock.Lock()
i.Closed = true
i.RWLock.Unlock()
return
}
func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) {
if i.NfsSKeys == nil {
return nil, errors.New("uninitialized")
}
c := NewCommonConn(conn, true)
ivAndRelays := make([]byte, 16+i.RelaysLength)
if _, err := io.ReadFull(conn, ivAndRelays); err != nil {
return nil, err
}
if fallback != nil {
*fallback = append(*fallback, ivAndRelays...)
}
iv := ivAndRelays[:16]
relays := ivAndRelays[16:]
var nfsKey []byte
var lastCTR cipher.Stream
for j, k := range i.NfsSKeys {
if lastCTR != nil {
lastCTR.XORKeyStream(relays, relays[:32]) // recover this relay
}
var index = 32
if _, ok := k.(*mlkem.DecapsulationKey768); ok {
index = 1088
}
if i.XorMode > 0 {
NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // we don't use buggy elligator2, because we have PSK :)
}
if k, ok := k.(*ecdh.PrivateKey); ok {
publicKey, err := ecdh.X25519().NewPublicKey(relays[:index])
if err != nil {
return nil, err
}
if publicKey.Bytes()[31] > 127 { // we just don't want the observer can change even one bit without breaking the connection, though it has nothing to do with security
return nil, errors.New("the highest bit of the last byte of the peer-sent X25519 public key is not 0")
}
nfsKey, err = k.ECDH(publicKey)
if err != nil {
return nil, err
}
}
if k, ok := k.(*mlkem.DecapsulationKey768); ok {
var err error
nfsKey, err = k.Decapsulate(relays[:index])
if err != nil {
return nil, err
}
}
if j == len(i.NfsSKeys)-1 {
break
}
relays = relays[index:]
lastCTR = NewCTR(nfsKey, iv)
lastCTR.XORKeyStream(relays, relays[:32])
if !bytes.Equal(relays[:32], i.Hash32s[j+1][:]) {
return nil, errors.New("unexpected hash32: ", fmt.Sprintf("%v", relays[:32]))
}
relays = relays[32:]
}
nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
encryptedLength := make([]byte, 18)
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err
}
if fallback != nil {
*fallback = append(*fallback, encryptedLength...)
}
decryptedLength := make([]byte, 2)
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
c.UseAES = !c.UseAES
nfsAEAD = NewAEAD(iv, nfsKey, c.UseAES)
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err
}
}
if fallback != nil {
*fallback = nil
}
length := DecodeLength(decryptedLength)
if length == 32 {
if i.SecondsFrom == 0 && i.SecondsTo == 0 {
return nil, errors.New("0-RTT is not allowed")
}
encryptedTicket := make([]byte, 32)
if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
return nil, err
}
ticket, err := nfsAEAD.Open(nil, nil, encryptedTicket, nil)
if err != nil {
return nil, err
}
i.RWLock.RLock()
s := i.Sessions[[16]byte(ticket)]
i.RWLock.RUnlock()
if s == nil {
noises := make([]byte, crypto.RandBetween(1279, 2279)) // matches 1-RTT's server hello length for "random", though it is not important, just for example
var err error
for err == nil {
rand.Read(noises)
_, err = DecodeHeader(noises)
}
conn.Write(noises) // make client do new handshake
return nil, errors.New("expired ticket")
}
if _, loaded := s.NfsKeys.LoadOrStore([32]byte(nfsKey), true); loaded { // prevents bad client also
return nil, errors.New("replay detected")
}
c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request)
c.PreWrite = make([]byte, 16)
rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub")
c.AEAD = NewAEAD(c.PreWrite, c.UnitedKey, c.UseAES)
c.PeerAEAD = NewAEAD(encryptedTicket, c.UnitedKey, c.UseAES) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client)
if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client
}
return c, nil
}
if length < 1184+32+16 { // client may send more public keys in the future's version
return nil, errors.New("too short length")
}
encryptedPfsPublicKey := make([]byte, length)
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
return nil, err
}
if _, err := nfsAEAD.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil {
return nil, err
}
mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184])
if err != nil {
return nil, err
}
mlkem768Key, encapsulatedPfsKey := mlkem768EKey.Encapsulate()
peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1184 : 1184+32])
if err != nil {
return nil, err
}
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
x25519Key, err := x25519SKey.ECDH(peerX25519PKey)
if err != nil {
return nil, err
}
pfsKey := make([]byte, 32+32) // no more capacity
copy(pfsKey, mlkem768Key)
copy(pfsKey[32:], x25519Key)
pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...)
c.UnitedKey = append(pfsKey, nfsKey...)
c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES)
ticket := [16]byte{}
rand.Read(ticket[:])
var seconds int64
if i.SecondsTo == 0 {
seconds = i.SecondsFrom * crypto.RandBetween(50, 100) / 100
} else {
seconds = crypto.RandBetween(i.SecondsFrom, i.SecondsTo)
}
copy(ticket[:], EncodeLength(int(seconds)))
if seconds > 0 {
i.RWLock.Lock()
i.Lasts[(time.Now().Unix()+max(i.SecondsFrom, i.SecondsTo))/60+2] = ticket
i.Tickets = append(i.Tickets, ticket)
i.Sessions[ticket] = &ServerSession{PfsKey: pfsKey}
i.RWLock.Unlock()
}
pfsKeyExchangeLength := 1088 + 32 + 16
encryptedTicketLength := 32
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket[:], nil)
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0]
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
if l > 0 {
if _, err := conn.Write(serverHello[:l]); err != nil {
return nil, err
}
serverHello = serverHello[l:]
}
if len(paddingGaps) > i {
time.Sleep(paddingGaps[i])
}
}
// important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
return nil, err
}
if _, err := nfsAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
return nil, err
}
encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2]))
if _, err := io.ReadFull(conn, encryptedPadding); err != nil {
return nil, err
}
if _, err := nfsAEAD.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil {
return nil, err
}
if i.XorMode == 2 {
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, ticket[:]), NewCTR(c.UnitedKey, iv), 0, 0)
}
return c, nil
}
-93
View File
@@ -1,93 +0,0 @@
package encryption
import (
"crypto/aes"
"crypto/cipher"
"net"
"lukechampine.com/blake3"
)
func NewCTR(key, iv []byte) cipher.Stream {
k := make([]byte, 32)
blake3.DeriveKey(k, "VLESS", key) // avoids using key directly
block, _ := aes.NewCipher(k)
return cipher.NewCTR(block, iv)
//chacha20.NewUnauthenticatedCipher()
}
type XorConn struct {
net.Conn
CTR cipher.Stream
PeerCTR cipher.Stream
OutSkip int
OutHeader []byte
InSkip int
InHeader []byte
}
func NewXorConn(conn net.Conn, ctr, peerCTR cipher.Stream, outSkip, inSkip int) *XorConn {
return &XorConn{
Conn: conn,
CTR: ctr,
PeerCTR: peerCTR,
OutSkip: outSkip,
OutHeader: make([]byte, 0, 5), // important
InSkip: inSkip,
InHeader: make([]byte, 0, 5), // important
}
}
func (c *XorConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
for p := b; ; {
if len(p) <= c.OutSkip {
c.OutSkip -= len(p)
break
}
p = p[c.OutSkip:]
c.OutSkip = 0
need := 5 - len(c.OutHeader)
if len(p) < need {
c.OutHeader = append(c.OutHeader, p...)
c.CTR.XORKeyStream(p, p)
break
}
c.OutSkip, _ = DecodeHeader(append(c.OutHeader, p[:need]...))
c.OutHeader = c.OutHeader[:0]
c.CTR.XORKeyStream(p[:need], p[:need])
p = p[need:]
}
if _, err := c.Conn.Write(b); err != nil {
return 0, err
}
return len(b), nil
}
func (c *XorConn) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
n, err := c.Conn.Read(b)
for p := b[:n]; ; {
if len(p) <= c.InSkip {
c.InSkip -= len(p)
break
}
p = p[c.InSkip:]
c.InSkip = 0
need := 5 - len(c.InHeader)
if len(p) < need {
c.PeerCTR.XORKeyStream(p, p)
c.InHeader = append(c.InHeader, p...)
break
}
c.PeerCTR.XORKeyStream(p[:need], p[:need])
c.InSkip, _ = DecodeHeader(append(c.InHeader, p[:need]...))
c.InHeader = c.InHeader[:0]
p = p[need:]
}
return n, err
}
+21 -58
View File
@@ -111,13 +111,11 @@ type Config struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"` Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` // Decryption settings. Only applies to server side, and only accepts "none"
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"` // for now.
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"`
SecondsFrom int64 `protobuf:"varint,5,opt,name=seconds_from,json=secondsFrom,proto3" json:"seconds_from,omitempty"` Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
SecondsTo int64 `protobuf:"varint,6,opt,name=seconds_to,json=secondsTo,proto3" json:"seconds_to,omitempty"`
Padding string `protobuf:"bytes,7,opt,name=padding,proto3" json:"padding,omitempty"`
} }
func (x *Config) Reset() { func (x *Config) Reset() {
@@ -157,13 +155,6 @@ func (x *Config) GetClients() []*protocol.User {
return nil return nil
} }
func (x *Config) GetFallbacks() []*Fallback {
if x != nil {
return x.Fallbacks
}
return nil
}
func (x *Config) GetDecryption() string { func (x *Config) GetDecryption() string {
if x != nil { if x != nil {
return x.Decryption return x.Decryption
@@ -171,32 +162,11 @@ func (x *Config) GetDecryption() string {
return "" return ""
} }
func (x *Config) GetXorMode() uint32 { func (x *Config) GetFallbacks() []*Fallback {
if x != nil { if x != nil {
return x.XorMode return x.Fallbacks
} }
return 0 return nil
}
func (x *Config) GetSecondsFrom() int64 {
if x != nil {
return x.SecondsFrom
}
return 0
}
func (x *Config) GetSecondsTo() int64 {
if x != nil {
return x.SecondsTo
}
return 0
}
func (x *Config) GetPadding() string {
if x != nil {
return x.Padding
}
return ""
} }
var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor
@@ -215,32 +185,25 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65,
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0x96, 0x02, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xa0, 0x01,
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x40, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1e,
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40,
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x46, 0x61, 0x6c, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x46, 0x61, 0x6c,
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73,
0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72,
0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
0x52, 0x0b, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x1d, 0x0a, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56,
0x0a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x74, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72,
0x03, 0x52, 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x54, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x74, 0x6f, 0x33,
0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70,
0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69,
0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63,
0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f,
0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50,
0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75,
0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
+4 -7
View File
@@ -19,11 +19,8 @@ message Fallback {
message Config { message Config {
repeated xray.common.protocol.User clients = 1; repeated xray.common.protocol.User clients = 1;
repeated Fallback fallbacks = 2; // Decryption settings. Only applies to server side, and only accepts "none"
// for now.
string decryption = 3; string decryption = 2;
uint32 xorMode = 4; repeated Fallback fallbacks = 3;
int64 seconds_from = 5;
int64 seconds_to = 6;
string padding = 7;
} }
+80 -48
View File
@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
gotls "crypto/tls" gotls "crypto/tls"
"encoding/base64"
"io" "io"
"reflect" "reflect"
"strconv" "strconv"
@@ -30,8 +29,6 @@ import (
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/encoding" "github.com/xtls/xray-core/proxy/vless/encoding"
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport"
"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"
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
@@ -70,7 +67,6 @@ type Handler struct {
policyManager policy.Manager policyManager policy.Manager
validator vless.Validator validator vless.Validator
dns dns.Client dns dns.Client
decryption *encryption.ServerInstance
fallbacks map[string]map[string]map[string]*Fallback // or nil fallbacks map[string]map[string]map[string]*Fallback // or nil
// regexps map[string]*regexp.Regexp // or nil // regexps map[string]*regexp.Regexp // or nil
} }
@@ -85,19 +81,6 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
validator: validator, validator: validator,
} }
if config.Decryption != "" && config.Decryption != "none" {
s := strings.Split(config.Decryption, ".")
var nfsSKeysBytes [][]byte
for _, r := range s {
b, _ := base64.RawURLEncoding.DecodeString(r)
nfsSKeysBytes = append(nfsSKeysBytes, b)
}
handler.decryption = &encryption.ServerInstance{}
if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.SecondsFrom, config.SecondsTo, config.Padding); err != nil {
return nil, errors.New("failed to use decryption").Base(err).AtError()
}
}
if config.Fallbacks != nil { if config.Fallbacks != nil {
handler.fallbacks = make(map[string]map[string]map[string]*Fallback) handler.fallbacks = make(map[string]map[string]map[string]*Fallback)
// handler.regexps = make(map[string]*regexp.Regexp) // handler.regexps = make(map[string]*regexp.Regexp)
@@ -176,9 +159,6 @@ func isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool {
// Close implements common.Closable.Close(). // Close implements common.Closable.Close().
func (h *Handler) Close() error { func (h *Handler) Close() error {
if h.decryption != nil {
h.decryption.Close()
}
return errors.Combine(common.Close(h.validator)) return errors.Combine(common.Close(h.validator))
} }
@@ -219,13 +199,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
iConn = statConn.Connection iConn = statConn.Connection
} }
if h.decryption != nil {
var err error
if connection, err = h.decryption.Handshake(connection, nil); err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
}
}
sessionPolicy := h.policyManager.ForLevel(0) sessionPolicy := h.policyManager.ForLevel(0)
if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return errors.New("unable to set read deadline").Base(err).AtWarning() return errors.New("unable to set read deadline").Base(err).AtWarning()
@@ -505,13 +478,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
case protocol.RequestCommandTCP: case protocol.RequestCommandTCP:
var t reflect.Type var t reflect.Type
var p uintptr var p uintptr
if commonConn, ok := connection.(*encryption.CommonConn); ok { if tlsConn, ok := iConn.(*tls.Conn); ok {
if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransportWithoutSecurity(iConn) {
inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated
}
t = reflect.TypeOf(commonConn).Elem()
p = uintptr(unsafe.Pointer(commonConn))
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { if tlsConn.ConnectionState().Version != gotls.VersionTLS13 {
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning() return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning()
} }
@@ -552,24 +519,89 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
ctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP) ctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP)
} }
sessionPolicy = h.policyManager.ForLevel(request.User.Level)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
inbound.Timer = timer
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
link, err := dispatcher.Dispatch(ctx, request.Destination())
if err != nil {
return errors.New("failed to dispatch request to ", request.Destination()).Base(err).AtWarning()
}
serverReader := link.Reader // .(*pipe.Reader)
serverWriter := link.Writer // .(*pipe.Writer)
trafficState := proxy.NewTrafficState(userSentID) trafficState := proxy.NewTrafficState(userSentID)
clientReader := encoding.DecodeBodyAddons(reader, request, requestAddons) postRequest := func() error {
if requestAddons.Flow == vless.XRV { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx, connection, input, rawInput, nil)
// default: clientReader := reader
clientReader := encoding.DecodeBodyAddons(reader, request, requestAddons)
var err error
if requestAddons.Flow == vless.XRV {
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice
clientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx1)
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, input, rawInput, trafficState, nil, true, ctx1)
} else {
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
}
if err != nil {
return errors.New("failed to transfer request payload").Base(err).AtInfo()
}
return nil
} }
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection)) getResponse := func() error {
if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil { defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
return errors.New("failed to encode response header").Base(err).AtWarning()
}
clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, false, ctx, connection, nil)
bufferWriter.SetFlushNext()
if err := dispatcher.DispatchLink(ctx, request.Destination(), &transport.Link{ bufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection))
Reader: clientReader, if err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil {
Writer: clientWriter}, return errors.New("failed to encode response header").Base(err).AtWarning()
); err != nil { }
return errors.New("failed to dispatch request").Base(err)
// default: clientWriter := bufferWriter
clientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, false, ctx)
multiBuffer, err1 := serverReader.ReadMultiBuffer()
if err1 != nil {
return err1 // ...
}
if err := clientWriter.WriteMultiBuffer(multiBuffer); err != nil {
return err // ...
}
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
if err := bufferWriter.SetBuffered(false); err != nil {
return errors.New("failed to write A response payload").Base(err).AtWarning()
}
var err error
if requestAddons.Flow == vless.XRV {
err = encoding.XtlsWrite(serverReader, clientWriter, timer, connection, trafficState, nil, false, ctx)
} else {
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
}
if err != nil {
return errors.New("failed to transfer response payload").Base(err).AtInfo()
}
// Indicates the end of response payload.
switch responseAddons.Flow {
default:
}
return nil
} }
if err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), getResponse); err != nil {
common.Interrupt(serverReader)
common.Interrupt(serverWriter)
return errors.New("connection ends").Base(err).AtInfo()
}
return nil return nil
} }
+10 -36
View File
@@ -4,9 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
gotls "crypto/tls" gotls "crypto/tls"
"encoding/base64"
"reflect" "reflect"
"strings"
"time" "time"
"unsafe" "unsafe"
@@ -26,7 +24,6 @@ import (
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless" "github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/encoding" "github.com/xtls/xray-core/proxy/vless/encoding"
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"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"
@@ -46,7 +43,6 @@ type Handler struct {
serverPicker protocol.ServerPicker serverPicker protocol.ServerPicker
policyManager policy.Manager policyManager policy.Manager
cone bool cone bool
encryption *encryption.ClientInstance
} }
// New creates a new VLess outbound handler. // New creates a new VLess outbound handler.
@@ -68,20 +64,6 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
cone: ctx.Value("cone").(bool), cone: ctx.Value("cone").(bool),
} }
a := handler.serverPicker.PickServer().PickUser().Account.(*vless.MemoryAccount)
if a.Encryption != "" && a.Encryption != "none" {
s := strings.Split(a.Encryption, ".")
var nfsPKeysBytes [][]byte
for _, r := range s {
b, _ := base64.RawURLEncoding.DecodeString(r)
nfsPKeysBytes = append(nfsPKeysBytes, b)
}
handler.encryption = &encryption.ClientInstance{}
if err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds, a.Padding); err != nil {
return nil, errors.New("failed to use encryption").Base(err).AtError()
}
}
return handler, nil return handler, nil
} }
@@ -116,13 +98,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
target := ob.Target target := ob.Target
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr()) errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr())
if h.encryption != nil {
var err error
if conn, err = h.encryption.Handshake(conn); err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
}
}
command := protocol.RequestCommandTCP command := protocol.RequestCommandTCP
if target.Network == net.Network_UDP { if target.Network == net.Network_UDP {
command = protocol.RequestCommandUDP command = protocol.RequestCommandUDP
@@ -165,13 +140,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
case protocol.RequestCommandTCP: case protocol.RequestCommandTCP:
var t reflect.Type var t reflect.Type
var p uintptr var p uintptr
if commonConn, ok := conn.(*encryption.CommonConn); ok { if tlsConn, ok := iConn.(*tls.Conn); ok {
if _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransportWithoutSecurity(iConn) {
ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated
}
t = reflect.TypeOf(commonConn).Elem()
p = uintptr(unsafe.Pointer(commonConn))
} else if tlsConn, ok := iConn.(*tls.Conn); ok {
t = reflect.TypeOf(tlsConn.Conn).Elem() t = reflect.TypeOf(tlsConn.Conn).Elem()
p = uintptr(unsafe.Pointer(tlsConn.Conn)) p = uintptr(unsafe.Pointer(tlsConn.Conn))
} else if utlsConn, ok := iConn.(*tls.UConn); ok { } else if utlsConn, ok := iConn.(*tls.UConn); ok {
@@ -225,7 +194,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
// default: serverWriter := bufferWriter // default: serverWriter := bufferWriter
serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, true, ctx, conn, ob) serverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, true, ctx)
if request.Command == protocol.RequestCommandMux && request.Port == 666 { if request.Command == protocol.RequestCommandMux && request.Port == 666 {
serverWriter = xudp.NewPacketWriter(serverWriter, target, xudp.GetGlobalID(ctx)) serverWriter = xudp.NewPacketWriter(serverWriter, target, xudp.GetGlobalID(ctx))
} }
@@ -253,6 +222,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return errors.New("failed to write A request payload").Base(err).AtWarning() return errors.New("failed to write A request payload").Base(err).AtWarning()
} }
var err error
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
if tlsConn, ok := iConn.(*tls.Conn); ok { if tlsConn, ok := iConn.(*tls.Conn); ok {
if tlsConn.ConnectionState().Version != gotls.VersionTLS13 { if tlsConn.ConnectionState().Version != gotls.VersionTLS13 {
@@ -263,8 +233,12 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, utlsConn.ConnectionState().Version).AtWarning() return errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, utlsConn.ConnectionState().Version).AtWarning()
} }
} }
ctx1 := session.ContextWithInbound(ctx, nil) // TODO enable splice
err = encoding.XtlsWrite(clientReader, serverWriter, timer, conn, trafficState, ob, true, ctx1)
} else {
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
} }
err := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
if err != nil { if err != nil {
return errors.New("failed to transfer request payload").Base(err).AtInfo() return errors.New("failed to transfer request payload").Base(err).AtInfo()
} }
@@ -287,7 +261,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
// default: serverReader := buf.NewReader(conn) // default: serverReader := buf.NewReader(conn)
serverReader := encoding.DecodeBodyAddons(conn, request, responseAddons) serverReader := encoding.DecodeBodyAddons(conn, request, responseAddons)
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
serverReader = proxy.NewVisionReader(serverReader, trafficState, false, ctx, conn, input, rawInput, ob) serverReader = proxy.NewVisionReader(serverReader, trafficState, false, ctx)
} }
if request.Command == protocol.RequestCommandMux && request.Port == 666 { if request.Command == protocol.RequestCommandMux && request.Port == 666 {
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
@@ -298,7 +272,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
if requestAddons.Flow == vless.XRV { if requestAddons.Flow == vless.XRV {
err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, trafficState, false, ctx) err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ob, false, ctx)
} else { } else {
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer // from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer)) err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
+2 -2
View File
@@ -10,10 +10,10 @@ import (
"hash/crc32" "hash/crc32"
"io" "io"
"math" "math"
"time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/antireplay" "github.com/xtls/xray-core/common/antireplay"
"github.com/xtls/xray-core/proxy/vmess/vtime"
) )
var ( var (
@@ -105,7 +105,7 @@ func (a *AuthIDDecoderHolder) Match(authID [16]byte) (interface{}, error) {
continue continue
} }
if math.Abs(math.Abs(float64(t))-float64(vtime.Now().Unix())) > 120 { if math.Abs(math.Abs(float64(t))-float64(time.Now().Unix())) > 120 {
continue continue
} }
+2 -2
View File
@@ -5,14 +5,14 @@ import (
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"io" "io"
"time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/proxy/vmess/vtime"
) )
func SealVMessAEADHeader(key [16]byte, data []byte) []byte { func SealVMessAEADHeader(key [16]byte, data []byte) []byte {
generatedAuthID := CreateAuthID(key[:], vtime.Now().Unix()) generatedAuthID := CreateAuthID(key[:], time.Now().Unix())
connectionNonce := make([]byte, 8) connectionNonce := make([]byte, 8)
if _, err := io.ReadFull(rand.Reader, connectionNonce); err != nil { if _, err := io.ReadFull(rand.Reader, connectionNonce); err != nil {
-74
View File
@@ -1,74 +0,0 @@
package vtime
import (
"context"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/protocol/http"
)
var timeOffset atomic.Pointer[time.Duration]
var initOnce sync.Once
func updateTimeMonitor(ctx context.Context, domain string) {
for {
select {
case <-ctx.Done():
return
case <-time.After(10 * time.Minute):
err := updateTime(domain)
if err != nil {
errors.LogError(ctx, err)
}
}
}
}
func updateTime(domain string) error {
httpClient := http.NewClient()
resp, err := httpClient.Get(domain)
if err != nil {
return errors.New("Failed to access monitor domain").Base(err)
}
timeHeader := resp.Header.Get("Date")
remoteTime, err := time.Parse(time.RFC1123, timeHeader)
if err != nil {
return errors.New("Failed to parse time from monitor domain").Base(err)
}
localTime := time.Now()
offset := remoteTime.Sub(localTime)
if offset < 2*time.Second && offset > -2*time.Second {
errors.LogWarning(context.Background(), "Time offset too small, ignoring:", offset)
return nil
}
timeOffset.Store(&offset)
return nil
}
func Now() time.Time {
initOnce.Do(func() {
timeOffset.Store(new(time.Duration))
go func() {
domain := platform.NewEnvFlag("xray.vmess.time.domain").GetValue(func() string { return "https://apple.com" })
if domain == "" {
errors.LogError(context.Background(), "vmess time domain is empty, skip time sync")
return
}
err := updateTime(domain)
if err != nil {
errors.LogError(context.Background(), err)
}
errors.LogWarning(context.Background(), "Initial time offset for vmess:", timeOffset.Load())
// only one sync should be enough, so disable periodic update for now
//go updateTimeMonitor(context.TODO(), domain)
}()
runtime.Gosched()
})
offset := timeOffset.Load()
return time.Now().Add(*offset)
}
+1 -2
View File
@@ -129,8 +129,7 @@ func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer)
} }
defer func() { defer func() {
if err != nil { if err != nil {
h.bind.Close() _ = h.bind.Close()
h.bind = nil
} }
}() }()
+1 -1
View File
@@ -249,7 +249,7 @@ func TestZeroBuffer(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
+2 -2
View File
@@ -182,7 +182,7 @@ func TestReverseProxy(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 32 { for i := 0; i < 32; i++ {
errg.Go(testTCPConn(externalPort, 10240*1024, time.Second*40)) errg.Go(testTCPConn(externalPort, 10240*1024, time.Second*40))
} }
@@ -374,7 +374,7 @@ func TestReverseProxyLongRunning(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
for range 4096 { for i := 0; i < 4096; i++ {
if err := testTCPConn(externalPort, 1024, time.Second*20)(); err != nil { if err := testTCPConn(externalPort, 1024, time.Second*20)(); err != nil {
t.Error(err) t.Error(err)
} }
+2 -2
View File
@@ -124,7 +124,7 @@ func testShadowsocks2022Tcp(t *testing.T, method string, password string) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errGroup errgroup.Group var errGroup errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
} }
@@ -207,7 +207,7 @@ func testShadowsocks2022Udp(t *testing.T, method string, password string) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errGroup errgroup.Group var errGroup errgroup.Group
for range 3 { for i := 0; i < 2; i++ {
errGroup.Go(testUDPConn(udpClientPort, 1024, time.Second*5)) errGroup.Go(testUDPConn(udpClientPort, 1024, time.Second*5))
} }
+5 -5
View File
@@ -96,7 +96,7 @@ func TestShadowsocksChaCha20Poly1305TCP(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errGroup errgroup.Group var errGroup errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
} }
if err := errGroup.Wait(); err != nil { if err := errGroup.Wait(); err != nil {
@@ -192,7 +192,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errGroup errgroup.Group var errGroup errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
} }
@@ -289,7 +289,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errGroup errgroup.Group var errGroup errgroup.Group
for range 3 { for i := 0; i < 2; i++ {
errGroup.Go(testUDPConn(clientPort, 1024, time.Second*5)) errGroup.Go(testUDPConn(clientPort, 1024, time.Second*5))
} }
if err := errGroup.Wait(); err != nil { if err := errGroup.Wait(); err != nil {
@@ -391,7 +391,7 @@ func TestShadowsocksAES128GCMUDPMux(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errGroup errgroup.Group var errGroup errgroup.Group
for range 3 { for i := 0; i < 2; i++ {
errGroup.Go(testUDPConn(clientPort, 1024, time.Second*5)) errGroup.Go(testUDPConn(clientPort, 1024, time.Second*5))
} }
if err := errGroup.Wait(); err != nil { if err := errGroup.Wait(); err != nil {
@@ -477,7 +477,7 @@ func TestShadowsocksNone(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errGroup errgroup.Group var errGroup errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
} }
+4 -4
View File
@@ -240,7 +240,7 @@ func TestAutoIssuingCertificate(t *testing.T) {
common.Must(err) common.Must(err)
defer CloseAllServers(servers) defer CloseAllServers(servers)
for range 3 { for i := 0; i < 10; i++ {
if err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil { if err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {
t.Error(err) t.Error(err)
} }
@@ -449,7 +449,7 @@ func TestTLSOverWebSocket(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -565,7 +565,7 @@ func TestGRPC(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40)) errg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -681,7 +681,7 @@ func TestGRPCMultiMode(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40)) errg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
+4 -4
View File
@@ -117,7 +117,7 @@ func TestVless(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -239,7 +239,7 @@ func TestVlessTls(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -363,7 +363,7 @@ func TestVlessXtlsVision(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -502,7 +502,7 @@ func TestVlessXtlsVisionReality(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 1; i++ {
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
+12 -12
View File
@@ -260,7 +260,7 @@ func TestVMessGCM(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
} }
@@ -366,7 +366,7 @@ func TestVMessGCMReadv(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -465,7 +465,7 @@ func TestVMessGCMUDP(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 2; i++ {
errg.Go(testUDPConn(clientPort, 1024, time.Second*5)) errg.Go(testUDPConn(clientPort, 1024, time.Second*5))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -564,7 +564,7 @@ func TestVMessChacha20(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20)) errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
} }
@@ -664,7 +664,7 @@ func TestVMessNone(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -771,7 +771,7 @@ func TestVMessKCP(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 2; i++ {
errg.Go(testTCPConn(clientPort, 1024, time.Minute*2)) errg.Go(testTCPConn(clientPort, 1024, time.Minute*2))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -915,7 +915,7 @@ func TestVMessKCPLarge(t *testing.T) {
common.Must(err) common.Must(err)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 2; i++ {
errg.Go(testTCPConn(clientPort, 513*1024, time.Minute*5)) errg.Go(testTCPConn(clientPort, 513*1024, time.Minute*5))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -1026,7 +1026,7 @@ func TestVMessGCMMux(t *testing.T) {
for range "abcd" { for range "abcd" {
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 16; i++ {
errg.Go(testTCPConn(clientPort, 10240, time.Second*20)) errg.Go(testTCPConn(clientPort, 10240, time.Second*20))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -1152,7 +1152,7 @@ func TestVMessGCMMuxUDP(t *testing.T) {
for range "ab" { for range "ab" {
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 2; i++ {
errg.Go(testTCPConn(clientPort, 1024, time.Second*10)) errg.Go(testTCPConn(clientPort, 1024, time.Second*10))
errg.Go(testUDPConn(clientUDPPort, 1024, time.Second*10)) errg.Go(testUDPConn(clientUDPPort, 1024, time.Second*10))
} }
@@ -1259,7 +1259,7 @@ func TestVMessZero(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30)) errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
} }
if err := errg.Wait(); err != nil { if err := errg.Wait(); err != nil {
@@ -1361,7 +1361,7 @@ func TestVMessGCMLengthAuth(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
} }
@@ -1465,7 +1465,7 @@ func TestVMessGCMLengthAuthPlusNoTerminationSignal(t *testing.T) {
defer CloseAllServers(servers) defer CloseAllServers(servers)
var errg errgroup.Group var errg errgroup.Group
for range 3 { for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40)) errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
} }
+24 -36
View File
@@ -22,24 +22,8 @@ type ResponseCallback func(ctx context.Context, packet *udp.Packet)
type connEntry struct { type connEntry struct {
link *transport.Link link *transport.Link
timer *signal.ActivityTimer timer signal.ActivityUpdater
cancel context.CancelFunc cancel context.CancelFunc
closed bool
}
func (c *connEntry) Close() error {
c.timer.SetTimeout(0)
return nil
}
func (c *connEntry) terminate() {
if c.closed {
panic("terminate called more than once")
}
c.closed = true
c.cancel()
common.Interrupt(c.link.Reader)
common.Interrupt(c.link.Writer)
} }
type Dispatcher struct { type Dispatcher struct {
@@ -48,7 +32,6 @@ type Dispatcher struct {
dispatcher routing.Dispatcher dispatcher routing.Dispatcher
callback ResponseCallback callback ResponseCallback
callClose func() error callClose func() error
closed bool
} }
func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Dispatcher { func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Dispatcher {
@@ -61,9 +44,13 @@ func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Di
func (v *Dispatcher) RemoveRay() { func (v *Dispatcher) RemoveRay() {
v.Lock() v.Lock()
defer v.Unlock() defer v.Unlock()
v.closed = true v.removeRay()
}
func (v *Dispatcher) removeRay() {
if v.conn != nil { if v.conn != nil {
v.conn.Close() common.Interrupt(v.conn.link.Reader)
common.Close(v.conn.link.Writer)
v.conn = nil v.conn = nil
} }
} }
@@ -72,34 +59,35 @@ func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) (*
v.Lock() v.Lock()
defer v.Unlock() defer v.Unlock()
if v.closed {
return nil, errors.New("dispatcher is closed")
}
if v.conn != nil { if v.conn != nil {
if v.conn.closed { return v.conn, nil
v.conn = nil
} else {
return v.conn, nil
}
} }
errors.LogInfo(ctx, "establishing new connection for ", dest) errors.LogInfo(ctx, "establishing new connection for ", dest)
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
entry := &connEntry{}
removeRay := func() {
v.Lock()
defer v.Unlock()
// sometimes the entry is already removed by others, don't close again
if entry == v.conn {
cancel()
v.removeRay()
}
}
timer := signal.CancelAfterInactivity(ctx, removeRay, time.Minute)
link, err := v.dispatcher.Dispatch(ctx, dest) link, err := v.dispatcher.Dispatch(ctx, dest)
if err != nil { if err != nil {
cancel()
return nil, errors.New("failed to dispatch request to ", dest).Base(err) return nil, errors.New("failed to dispatch request to ", dest).Base(err)
} }
entry := &connEntry{ *entry = connEntry{
link: link, link: link,
cancel: cancel, timer: timer,
cancel: removeRay,
} }
entry.timer = signal.CancelAfterInactivity(ctx, entry.terminate, time.Minute)
v.conn = entry v.conn = entry
go handleInput(ctx, entry, dest, v.callback, v.callClose) go handleInput(ctx, entry, dest, v.callback, v.callClose)
return entry, nil return entry, nil
@@ -118,7 +106,7 @@ func (v *Dispatcher) Dispatch(ctx context.Context, destination net.Destination,
if outputStream != nil { if outputStream != nil {
if err := outputStream.WriteMultiBuffer(buf.MultiBuffer{payload}); err != nil { if err := outputStream.WriteMultiBuffer(buf.MultiBuffer{payload}); err != nil {
errors.LogInfoInner(ctx, err, "failed to write first UDP payload") errors.LogInfoInner(ctx, err, "failed to write first UDP payload")
conn.Close() conn.cancel()
return return
} }
} }
@@ -126,7 +114,7 @@ func (v *Dispatcher) Dispatch(ctx context.Context, destination net.Destination,
func handleInput(ctx context.Context, conn *connEntry, dest net.Destination, callback ResponseCallback, callClose func() error) { func handleInput(ctx context.Context, conn *connEntry, dest net.Destination, callback ResponseCallback, callClose func() error) {
defer func() { defer func() {
conn.Close() conn.cancel()
if callClose != nil { if callClose != nil {
callClose() callClose()
} }
+5 -8
View File
@@ -200,19 +200,16 @@ func (p *pipe) Interrupt() {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()
if !p.data.IsEmpty() {
buf.ReleaseMulti(p.data)
p.data = nil
if p.state == closed {
p.state = errord
}
}
if p.state == closed || p.state == errord { if p.state == closed || p.state == errord {
return return
} }
p.state = errord p.state = errord
if !p.data.IsEmpty() {
buf.ReleaseMulti(p.data)
p.data = nil
}
common.Must(p.done.Close()) common.Must(p.done.Close())
} }