mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-12-18 21:24:37 +03:00
Compare commits
21 Commits
remove-udp
...
buffer-tro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea508727de | ||
|
|
fbb0ecfb83 | ||
|
|
a31842feaa | ||
|
|
79325ead2e | ||
|
|
81b7cd718a | ||
|
|
ea1a3ae8f1 | ||
|
|
593ededd3e | ||
|
|
82ea7a3cc5 | ||
|
|
56a45ad578 | ||
|
|
4976085ddb | ||
|
|
fcdd4df446 | ||
|
|
12b077f33b | ||
|
|
702d2c06ca | ||
|
|
7951a5c4bf | ||
|
|
c2141f09e7 | ||
|
|
ef640ed309 | ||
|
|
5fa5f3fbb9 | ||
|
|
2ee372e758 | ||
|
|
11f0513bce | ||
|
|
b65da77267 | ||
|
|
33272a0499 |
@@ -11,8 +11,10 @@
|
||||
[<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`**
|
||||
- **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**
|
||||
- **Related links: https://opensea.io/collection/xtls, [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)**
|
||||
- **Related links: [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)**
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ var errSniffingTimeout = errors.New("timeout on sniffing")
|
||||
|
||||
type cachedReader struct {
|
||||
sync.Mutex
|
||||
reader *pipe.Reader
|
||||
reader buf.TimeoutReader // *pipe.Reader or *buf.TimeoutWrapperReader
|
||||
cache buf.MultiBuffer
|
||||
}
|
||||
|
||||
@@ -87,7 +87,9 @@ func (r *cachedReader) Interrupt() {
|
||||
r.cache = buf.ReleaseMulti(r.cache)
|
||||
}
|
||||
r.Unlock()
|
||||
r.reader.Interrupt()
|
||||
if p, ok := r.reader.(*pipe.Reader); ok {
|
||||
p.Interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultDispatcher is a default implementation of Dispatcher.
|
||||
@@ -319,7 +321,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
d.routedDispatch(ctx, outbound, destination)
|
||||
} else {
|
||||
cReader := &cachedReader{
|
||||
reader: outbound.Reader.(*pipe.Reader),
|
||||
reader: outbound.Reader.(buf.TimeoutReader),
|
||||
}
|
||||
outbound.Reader = cReader
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||
|
||||
@@ -329,7 +329,7 @@ func init() {
|
||||
}
|
||||
|
||||
func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) {
|
||||
conn4, err4 := net.Dial("udp4", "8.8.8.8:53")
|
||||
conn4, err4 := net.Dial("udp4", "192.33.4.12:53")
|
||||
if err4 != nil {
|
||||
supportIPv4 = false
|
||||
} else {
|
||||
@@ -337,7 +337,7 @@ func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) {
|
||||
conn4.Close()
|
||||
}
|
||||
|
||||
conn6, err6 := net.Dial("udp6", "[2001:4860:4860::8888]:53")
|
||||
conn6, err6 := net.Dial("udp6", "[2001:500:2::c]:53")
|
||||
if err6 != nil {
|
||||
supportIPv6 = false
|
||||
} else {
|
||||
|
||||
@@ -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 m.test.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME test.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8888")),
|
||||
common.Must2(dns.NewRR("google.com. IN AAAA 2001::123:8844")),
|
||||
common.Must2(dns.NewRR("google.com. IN AAAA 2001:4860:4860::8888")),
|
||||
common.Must2(dns.NewRR("google.com. IN AAAA 2001:4860:4860::8844")),
|
||||
)
|
||||
p = append(p, common.Must2(ans.Pack()))
|
||||
|
||||
@@ -72,7 +72,7 @@ func Test_parseResponse(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"aaaa record",
|
||||
&IPRecord{2, []net.IP{net.ParseIP("2001::123:8888"), net.ParseIP("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil},
|
||||
&IPRecord{2, []net.IP{net.ParseIP("2001:4860:4860::8888"), net.ParseIP("2001:4860:4860::8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -90,7 +90,9 @@ func (s *ClassicNameServer) RequestsCleanup() error {
|
||||
|
||||
// HandleResponse handles udp response packet from remote DNS server.
|
||||
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
|
||||
ipRec, err := parseResponse(packet.Payload.Bytes())
|
||||
payload := packet.Payload
|
||||
ipRec, err := parseResponse(payload.Bytes())
|
||||
payload.Release()
|
||||
if err != nil {
|
||||
errors.LogError(ctx, s.Name(), " fail to parse responded DNS udp")
|
||||
return
|
||||
@@ -125,6 +127,8 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
|
||||
newReq.msg = &newMsg
|
||||
s.addPendingRequest(&newReq)
|
||||
b, _ := dns.PackMessage(newReq.msg)
|
||||
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||
b.UDP = ©Dest
|
||||
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
|
||||
return
|
||||
}
|
||||
@@ -158,6 +162,8 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, domai
|
||||
}
|
||||
s.addPendingRequest(udpReq)
|
||||
b, _ := dns.PackMessage(req.msg)
|
||||
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||
b.UDP = ©Dest
|
||||
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,8 +239,10 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
|
||||
}
|
||||
out:
|
||||
err := h.proxy.Process(ctx, link, h)
|
||||
var errC error
|
||||
if err != nil {
|
||||
if goerrors.Is(err, io.EOF) || goerrors.Is(err, io.ErrClosedPipe) || goerrors.Is(err, context.Canceled) {
|
||||
errC = errors.Cause(err)
|
||||
if goerrors.Is(errC, io.EOF) || goerrors.Is(errC, io.ErrClosedPipe) || goerrors.Is(errC, context.Canceled) {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
@@ -251,7 +253,11 @@ out:
|
||||
errors.LogInfo(ctx, err.Error())
|
||||
common.Interrupt(link.Writer)
|
||||
} else {
|
||||
common.Close(link.Writer)
|
||||
if errC != nil && goerrors.Is(errC, io.ErrClosedPipe) {
|
||||
common.Interrupt(link.Writer)
|
||||
} else {
|
||||
common.Close(link.Writer)
|
||||
}
|
||||
}
|
||||
common.Interrupt(link.Reader)
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ const (
|
||||
|
||||
var ErrBufferFull = errors.New("buffer is full")
|
||||
|
||||
var zero = [Size * 10]byte{0}
|
||||
|
||||
var pool = bytespool.GetPool(Size)
|
||||
|
||||
// ownership represents the data owner of the buffer.
|
||||
@@ -146,7 +144,7 @@ func (b *Buffer) Bytes() []byte {
|
||||
}
|
||||
|
||||
// Extend increases the buffer size by n bytes, and returns the extended part.
|
||||
// It panics if result size is larger than buf.Size.
|
||||
// It panics if result size is larger than size of this buffer.
|
||||
func (b *Buffer) Extend(n int32) []byte {
|
||||
end := b.end + n
|
||||
if end > int32(len(b.v)) {
|
||||
@@ -154,7 +152,7 @@ func (b *Buffer) Extend(n int32) []byte {
|
||||
}
|
||||
ext := b.v[b.end:end]
|
||||
b.end = end
|
||||
copy(ext, zero[:])
|
||||
clear(ext)
|
||||
return ext
|
||||
}
|
||||
|
||||
@@ -217,7 +215,7 @@ func (b *Buffer) Resize(from, to int32) {
|
||||
b.start += from
|
||||
b.Check()
|
||||
if b.end > oldEnd {
|
||||
copy(b.v[oldEnd:b.end], zero[:])
|
||||
clear(b.v[oldEnd:b.end])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,46 @@ var ErrReadTimeout = errors.New("IO timeout")
|
||||
|
||||
// TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout.
|
||||
type TimeoutReader interface {
|
||||
Reader
|
||||
ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error)
|
||||
}
|
||||
|
||||
type TimeoutWrapperReader struct {
|
||||
Reader
|
||||
mb MultiBuffer
|
||||
err error
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (r *TimeoutWrapperReader) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
if r.done != nil {
|
||||
<-r.done
|
||||
r.done = nil
|
||||
return r.mb, r.err
|
||||
}
|
||||
r.mb = nil
|
||||
r.err = nil
|
||||
return r.Reader.ReadMultiBuffer()
|
||||
}
|
||||
|
||||
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)
|
||||
}()
|
||||
}
|
||||
time.Sleep(duration)
|
||||
select {
|
||||
case <-r.done:
|
||||
r.done = nil
|
||||
return r.mb, r.err
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Writer extends io.Writer with MultiBuffer.
|
||||
type Writer interface {
|
||||
// WriteMultiBuffer writes a MultiBuffer into underlying writer.
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -153,3 +154,14 @@ func GetModuleName(pathToProjectRoot string) (string, error) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package mux
|
||||
|
||||
import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -154,8 +155,11 @@ func (f *DialingWorkerFactory) Create() (*ClientWorker, error) {
|
||||
ctx := session.ContextWithOutbounds(context.Background(), outbounds)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
if err := p.Process(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter}, d); err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to handler mux client connection")
|
||||
if errP := p.Process(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter}, d); errP != nil {
|
||||
errC := errors.Cause(errP)
|
||||
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())
|
||||
cancel()
|
||||
@@ -222,7 +226,7 @@ func (m *ClientWorker) monitor() {
|
||||
select {
|
||||
case <-m.done.Wait():
|
||||
m.sessionManager.Close()
|
||||
common.Close(m.link.Writer)
|
||||
common.Interrupt(m.link.Writer)
|
||||
common.Interrupt(m.link.Reader)
|
||||
return
|
||||
case <-m.timer.C:
|
||||
@@ -247,7 +251,7 @@ func writeFirstPayload(reader buf.Reader, writer *Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchInput(ctx context.Context, s *Session, output buf.Writer) {
|
||||
func fetchInput(ctx context.Context, s *Session, output buf.Writer, timer *time.Ticker) {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
transferType := protocol.TransferTypeStream
|
||||
@@ -258,6 +262,7 @@ func fetchInput(ctx context.Context, s *Session, output buf.Writer) {
|
||||
writer := NewWriter(s.ID, ob.Target, output, transferType, xudp.GetGlobalID(ctx))
|
||||
defer s.Close(false)
|
||||
defer writer.Close()
|
||||
defer timer.Reset(time.Second * 16)
|
||||
|
||||
errors.LogInfo(ctx, "dispatching request to ", ob.Target)
|
||||
if err := writeFirstPayload(s.input, writer); err != nil {
|
||||
@@ -307,7 +312,11 @@ func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool
|
||||
}
|
||||
s.input = link.Reader
|
||||
s.output = link.Writer
|
||||
go fetchInput(ctx, s, m.link.Writer)
|
||||
if _, ok := link.Reader.(*pipe.Reader); ok {
|
||||
go fetchInput(ctx, s, m.link.Writer, m.timer)
|
||||
} else {
|
||||
fetchInput(ctx, s, m.link.Writer, m.timer)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,14 @@ func NewServerWorker(ctx context.Context, d routing.Dispatcher, link *transport.
|
||||
link: link,
|
||||
sessionManager: NewSessionManager(),
|
||||
}
|
||||
go worker.run(ctx)
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||
inbound.CanSpliceCopy = 3
|
||||
}
|
||||
if _, ok := link.Reader.(*pipe.Reader); ok {
|
||||
go worker.run(ctx)
|
||||
} else {
|
||||
worker.run(ctx)
|
||||
}
|
||||
return worker, nil
|
||||
}
|
||||
|
||||
@@ -311,8 +318,8 @@ func (w *ServerWorker) run(ctx context.Context) {
|
||||
reader := &buf.BufferedReader{Reader: w.link.Reader}
|
||||
|
||||
defer w.sessionManager.Close()
|
||||
defer common.Close(w.link.Writer)
|
||||
defer common.Interrupt(w.link.Reader)
|
||||
defer common.Interrupt(w.link.Writer)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -79,20 +79,18 @@ type CommandSwitchAccount struct {
|
||||
}
|
||||
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
// Keep in sync with crypto/tls/cipher_suites.go.
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
|
||||
(cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
|
||||
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
|
||||
|
||||
hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 ||
|
||||
runtime.GOARCH == "arm64" && hasGCMAsmARM64 ||
|
||||
runtime.GOARCH == "s390x" && hasGCMAsmS390X
|
||||
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
|
||||
)
|
||||
|
||||
func (sc *SecurityConfig) GetSecurityType() SecurityType {
|
||||
if sc == nil || sc.Type == SecurityType_AUTO {
|
||||
if hasAESGCMHardwareSupport {
|
||||
if HasAESGCMHardwareSupport {
|
||||
return SecurityType_AES128_GCM
|
||||
}
|
||||
return SecurityType_CHACHA20_POLY1305
|
||||
|
||||
@@ -3,6 +3,7 @@ package signal
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
@@ -14,10 +15,12 @@ type ActivityUpdater interface {
|
||||
}
|
||||
|
||||
type ActivityTimer struct {
|
||||
sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
updated chan struct{}
|
||||
checkTask *task.Periodic
|
||||
onTimeout func()
|
||||
consumed atomic.Bool
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) Update() {
|
||||
@@ -37,39 +40,39 @@ func (t *ActivityTimer) check() error {
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) finish() {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.once.Do(func() {
|
||||
t.consumed.Store(true)
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.onTimeout != nil {
|
||||
common.CloseIfExists(t.checkTask)
|
||||
t.onTimeout()
|
||||
t.onTimeout = nil
|
||||
}
|
||||
if t.checkTask != nil {
|
||||
t.checkTask.Close()
|
||||
t.checkTask = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
|
||||
if t.consumed.Load() {
|
||||
return
|
||||
}
|
||||
if timeout == 0 {
|
||||
t.finish()
|
||||
return
|
||||
}
|
||||
|
||||
checkTask := &task.Periodic{
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
// double check, just in case
|
||||
if t.consumed.Load() {
|
||||
return
|
||||
}
|
||||
newCheckTask := &task.Periodic{
|
||||
Interval: timeout,
|
||||
Execute: t.check,
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
|
||||
if t.checkTask != nil {
|
||||
t.checkTask.Close()
|
||||
}
|
||||
t.checkTask = checkTask
|
||||
common.CloseIfExists(t.checkTask)
|
||||
t.checkTask = newCheckTask
|
||||
t.Update()
|
||||
common.Must(checkTask.Start())
|
||||
t.Unlock()
|
||||
common.Must(newCheckTask.Start())
|
||||
}
|
||||
|
||||
func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
var (
|
||||
Version_x byte = 25
|
||||
Version_y byte = 8
|
||||
Version_z byte = 3
|
||||
Version_z byte = 29
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
6
go.mod
6
go.mod
@@ -16,10 +16,10 @@ require (
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7
|
||||
github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/net v0.43.0
|
||||
@@ -27,7 +27,7 @@ require (
|
||||
golang.org/x/sys v0.35.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
google.golang.org/grpc v1.75.0
|
||||
google.golang.org/protobuf v1.36.7
|
||||
google.golang.org/protobuf v1.36.8
|
||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5
|
||||
h12.io/socks v1.0.3
|
||||
lukechampine.com/blake3 v1.4.1
|
||||
|
||||
12
go.sum
12
go.sum
@@ -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/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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
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/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
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/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 h1:Ript0vN+nSO33+Vj4n0mgNY5M+oOxFQJdrJ1VnwTBO0=
|
||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
||||
github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f h1:o1Kryl9qEYYzNep9RId9DM1kBn8tBrcK5UJnti/l0NI=
|
||||
github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
||||
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/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
@@ -145,8 +145,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -80,10 +81,45 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
|
||||
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
|
||||
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
|
||||
}
|
||||
if s[2] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[2], "s")
|
||||
if t == s[2] {
|
||||
return false
|
||||
}
|
||||
i, err := strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
config.Seconds = uint32(i)
|
||||
}
|
||||
for i := 3; i < len(s); i++ {
|
||||
if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 64 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
config.Decryption = config.Decryption[27+len(s[2]):]
|
||||
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)
|
||||
}
|
||||
|
||||
for _, fb := range c.Fallbacks {
|
||||
var i uint16
|
||||
@@ -155,16 +191,16 @@ type VLessOutboundConfig struct {
|
||||
func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
||||
config := new(outbound.Config)
|
||||
|
||||
if len(c.Vnext) == 0 {
|
||||
return nil, errors.New(`VLESS settings: "vnext" is empty`)
|
||||
if len(c.Vnext) != 1 {
|
||||
return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`)
|
||||
}
|
||||
config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext))
|
||||
for idx, rec := range c.Vnext {
|
||||
if rec.Address == nil {
|
||||
return nil, errors.New(`VLESS vnext: "address" is not set`)
|
||||
}
|
||||
if len(rec.Users) == 0 {
|
||||
return nil, errors.New(`VLESS vnext: "users" is empty`)
|
||||
if len(rec.Users) != 1 {
|
||||
return nil, errors.New(`VLESS vnext: "users" should have one and only one member`)
|
||||
}
|
||||
spec := &protocol.ServerEndpoint{
|
||||
Address: rec.Address.Build(),
|
||||
@@ -193,8 +229,39 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
|
||||
}
|
||||
|
||||
if account.Encryption != "none" {
|
||||
return nil, errors.New(`VLESS users: please add/set "encryption":"none" for every user`)
|
||||
if !func() bool {
|
||||
s := strings.Split(account.Encryption, ".")
|
||||
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
|
||||
}
|
||||
for i := 3; i < len(s); i++ {
|
||||
if b, _ := base64.RawURLEncoding.DecodeString(s[i]); len(b) != 32 && len(b) != 1184 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
account.Encryption = account.Encryption[27+len(s[2]):]
|
||||
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)
|
||||
|
||||
@@ -17,5 +17,6 @@ func init() {
|
||||
cmdX25519,
|
||||
cmdWG,
|
||||
cmdMLDSA65,
|
||||
cmdMLKEM768,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package convert
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/cmdarg"
|
||||
creflect "github.com/xtls/xray-core/common/reflect"
|
||||
@@ -14,15 +15,18 @@ import (
|
||||
|
||||
var cmdProtobuf = &base.Command{
|
||||
CustomFlags: true,
|
||||
UsageLine: "{{.Exec}} convert pb [-debug] [-type] [json file] [json file] ...",
|
||||
UsageLine: "{{.Exec}} convert pb [-outpbfile file] [-debug] [-type] [json file] [json file] ...",
|
||||
Short: "Convert multiple json configs to protobuf",
|
||||
Long: `
|
||||
Convert multiple json configs to protobuf.
|
||||
Convert multiple configs to ProtoBuf. JSON, YAML and TOML can be used.
|
||||
|
||||
Arguments:
|
||||
|
||||
-o file, -outpbfile file
|
||||
Write the ProtoBuf output (eg. mix.pb) to specified file location.
|
||||
|
||||
-d, -debug
|
||||
Show mix.pb as json.
|
||||
Show mix.pb as JSON format.
|
||||
FOR DEBUGGING ONLY!
|
||||
DO NOT PASS THIS OUTPUT TO XRAY-CORE!
|
||||
|
||||
@@ -31,16 +35,20 @@ Arguments:
|
||||
|
||||
Examples:
|
||||
|
||||
{{.Exec}} convert pb config.json c1.json c2.json c3.json > mix.pb
|
||||
{{.Exec}} convert pb -outpbfile output.pb config.json c1.json c2.json c3.json
|
||||
{{.Exec}} convert pb -debug mix.pb
|
||||
`,
|
||||
Run: executeConvertConfigsToProtobuf,
|
||||
}
|
||||
|
||||
func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
||||
|
||||
var optFile string
|
||||
var optDump bool
|
||||
var optType bool
|
||||
|
||||
cmd.Flag.StringVar(&optFile, "o", "", "")
|
||||
cmd.Flag.StringVar(&optFile, "outpbfile", "", "")
|
||||
cmd.Flag.BoolVar(&optDump, "d", false, "")
|
||||
cmd.Flag.BoolVar(&optDump, "debug", false, "")
|
||||
cmd.Flag.BoolVar(&optType, "t", false, "")
|
||||
@@ -52,6 +60,17 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
||||
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 {
|
||||
base.Fatalf("invalid config list length: %d", len(unnamedArgs))
|
||||
}
|
||||
@@ -70,12 +89,28 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
bytesConfig, err := proto.Marshal(pbConfig)
|
||||
if err != nil {
|
||||
base.Fatalf("failed to marshal proto config: %s", err)
|
||||
}
|
||||
if len(optFile) > 0 {
|
||||
bytesConfig, err := proto.Marshal(pbConfig)
|
||||
if err != nil {
|
||||
base.Fatalf("failed to marshal proto config: %s", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stdout.Write(bytesConfig); err != nil {
|
||||
base.Fatalf("failed to write proto config: %s", err)
|
||||
f, err := os.Create(optFile)
|
||||
if err != nil {
|
||||
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:]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
package all
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"lukechampine.com/blake3"
|
||||
)
|
||||
|
||||
func Curve25519Genkey(StdEncoding bool, input_base64 string) {
|
||||
var output string
|
||||
var err error
|
||||
var privateKey, publicKey []byte
|
||||
var encoding *base64.Encoding
|
||||
if *input_stdEncoding || StdEncoding {
|
||||
encoding = base64.StdEncoding
|
||||
@@ -19,40 +17,35 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
|
||||
encoding = base64.RawURLEncoding
|
||||
}
|
||||
|
||||
var privateKey []byte
|
||||
if len(input_base64) > 0 {
|
||||
privateKey, err = encoding.DecodeString(input_base64)
|
||||
if err != nil {
|
||||
output = err.Error()
|
||||
goto out
|
||||
}
|
||||
if len(privateKey) != curve25519.ScalarSize {
|
||||
output = "Invalid length of private key."
|
||||
goto out
|
||||
privateKey, _ = encoding.DecodeString(input_base64)
|
||||
if len(privateKey) != 32 {
|
||||
fmt.Println("Invalid length of X25519 private key.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if privateKey == nil {
|
||||
privateKey = make([]byte, curve25519.ScalarSize)
|
||||
if _, err = rand.Read(privateKey); err != nil {
|
||||
output = err.Error()
|
||||
goto out
|
||||
}
|
||||
privateKey = make([]byte, 32)
|
||||
rand.Read(privateKey)
|
||||
}
|
||||
|
||||
// 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[31] &= 127
|
||||
privateKey[31] |= 64
|
||||
|
||||
if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
|
||||
output = err.Error()
|
||||
goto out
|
||||
key, err := ecdh.X25519().NewPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
output = fmt.Sprintf("Private key: %v\nPublic key: %v",
|
||||
password := key.PublicKey().Bytes()
|
||||
hash32 := blake3.Sum256(password)
|
||||
fmt.Printf("PrivateKey: %v\nPassword: %v\nHash32: %v",
|
||||
encoding.EncodeToString(privateKey),
|
||||
encoding.EncodeToString(publicKey))
|
||||
out:
|
||||
fmt.Println(output)
|
||||
encoding.EncodeToString(password),
|
||||
encoding.EncodeToString(hash32[:]))
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
|
||||
var cmdMLDSA65 = &base.Command{
|
||||
UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`,
|
||||
Short: `Generate key pair for ML-DSA-65 post-quantum signature`,
|
||||
Short: `Generate key pair for ML-DSA-65 post-quantum signature (REALITY)`,
|
||||
Long: `
|
||||
Generate key pair for ML-DSA-65 post-quantum signature.
|
||||
Generate key pair for ML-DSA-65 post-quantum signature (REALITY).
|
||||
|
||||
Random: {{.Exec}} mldsa65
|
||||
|
||||
@@ -25,12 +25,16 @@ func init() {
|
||||
cmdMLDSA65.Run = executeMLDSA65 // break init loop
|
||||
}
|
||||
|
||||
var input_seed = cmdMLDSA65.Flag.String("i", "", "")
|
||||
var input_mldsa65 = cmdMLDSA65.Flag.String("i", "", "")
|
||||
|
||||
func executeMLDSA65(cmd *base.Command, args []string) {
|
||||
var seed [32]byte
|
||||
if len(*input_seed) > 0 {
|
||||
s, _ := base64.RawURLEncoding.DecodeString(*input_seed)
|
||||
if len(*input_mldsa65) > 0 {
|
||||
s, _ := base64.RawURLEncoding.DecodeString(*input_mldsa65)
|
||||
if len(s) != 32 {
|
||||
fmt.Println("Invalid length of ML-DSA-65 seed.")
|
||||
return
|
||||
}
|
||||
seed = [32]byte(s)
|
||||
} else {
|
||||
rand.Read(seed[:])
|
||||
|
||||
50
main/commands/all/mlkem768.go
Normal file
50
main/commands/all/mlkem768.go
Normal file
@@ -0,0 +1,50 @@
|
||||
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)`,
|
||||
Long: `
|
||||
Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS).
|
||||
|
||||
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[:])
|
||||
}
|
||||
key, _ := mlkem.NewDecapsulationKey768(seed[:])
|
||||
client := key.EncapsulationKey().Bytes()
|
||||
hash32 := blake3.Sum256(client)
|
||||
fmt.Printf("Seed: %v\nClient: %v\nHash32: %v",
|
||||
base64.RawURLEncoding.EncodeToString(seed[:]),
|
||||
base64.RawURLEncoding.EncodeToString(client),
|
||||
base64.RawURLEncoding.EncodeToString(hash32[:]))
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
var cmdUUID = &base.Command{
|
||||
UsageLine: `{{.Exec}} uuid [-i "example"]`,
|
||||
Short: `Generate UUIDv4 or UUIDv5`,
|
||||
Short: `Generate UUIDv4 or UUIDv5 (VLESS)`,
|
||||
Long: `
|
||||
Generate UUIDv4 or UUIDv5.
|
||||
Generate UUIDv4 or UUIDv5 (VLESS).
|
||||
|
||||
UUIDv4 (random): {{.Exec}} uuid
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
|
||||
var cmdWG = &base.Command{
|
||||
UsageLine: `{{.Exec}} wg [-i "private key (base64.StdEncoding)"]`,
|
||||
Short: `Generate key pair for wireguard key exchange`,
|
||||
Short: `Generate key pair for X25519 key exchange (WireGuard)`,
|
||||
Long: `
|
||||
Generate key pair for wireguard key exchange.
|
||||
Generate key pair for X25519 key exchange (WireGuard).
|
||||
|
||||
Random: {{.Exec}} wg
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
|
||||
var cmdX25519 = &base.Command{
|
||||
UsageLine: `{{.Exec}} x25519 [-i "private key (base64.RawURLEncoding)"] [--std-encoding]`,
|
||||
Short: `Generate key pair for x25519 key exchange`,
|
||||
Short: `Generate key pair for X25519 key exchange (VLESS, REALITY)`,
|
||||
Long: `
|
||||
Generate key pair for x25519 key exchange.
|
||||
Generate key pair for X25519 key exchange (VLESS, REALITY).
|
||||
|
||||
Random: {{.Exec}} x25519
|
||||
|
||||
|
||||
13
main/run.go
13
main/run.go
@@ -182,12 +182,15 @@ func getConfigFilePath(verbose bool) cmdarg.Arg {
|
||||
}
|
||||
|
||||
if workingDir, err := os.Getwd(); err == nil {
|
||||
configFile := filepath.Join(workingDir, "config.json")
|
||||
if fileExists(configFile) {
|
||||
if verbose {
|
||||
log.Println("Using default config: ", configFile)
|
||||
suffixes := []string{".json", ".jsonc", ".toml", ".yaml", ".yml"}
|
||||
for _, suffix := range suffixes {
|
||||
configFile := filepath.Join(workingDir, "config"+suffix)
|
||||
if fileExists(configFile) {
|
||||
if verbose {
|
||||
log.Println("Using default config: ", configFile)
|
||||
}
|
||||
return cmdarg.Arg{configFile}
|
||||
}
|
||||
return cmdarg.Arg{configFile}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,8 @@ package dokodemo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
@@ -14,11 +12,10 @@ import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"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/features/policy"
|
||||
"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/tls"
|
||||
)
|
||||
@@ -144,39 +141,11 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
|
||||
})
|
||||
errors.LogInfo(ctx, "received request for ", conn.RemoteAddr())
|
||||
|
||||
plcy := d.policy()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
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 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 reader buf.Reader
|
||||
if dest.Network == net.Network_TCP {
|
||||
reader = buf.NewReader(conn)
|
||||
} else {
|
||||
reader = buf.NewPacketReader(conn)
|
||||
}
|
||||
|
||||
var writer buf.Writer
|
||||
@@ -208,72 +177,17 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
|
||||
return err
|
||||
}
|
||||
writer = NewPacketWriter(pConn, &dest, mark, back)
|
||||
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
|
||||
}
|
||||
*/
|
||||
defer writer.(*PacketWriter).Close() // close fake UDP conns
|
||||
}
|
||||
}
|
||||
|
||||
responseDone := func() error {
|
||||
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
|
||||
|
||||
if network == net.Network_UDP && destinationOverridden {
|
||||
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
|
||||
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
|
||||
Reader: &buf.TimeoutWrapperReader{Reader: reader},
|
||||
Writer: writer},
|
||||
); err != nil {
|
||||
return errors.New("failed to dispatch request").Base(err)
|
||||
}
|
||||
|
||||
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
|
||||
return nil // Unlike Dispatch(), DispatchLink() will not return until the outbound finishes Process()
|
||||
}
|
||||
|
||||
func NewPacketWriter(conn net.PacketConn, d *net.Destination, mark int, back *net.UDPAddr) buf.Writer {
|
||||
|
||||
@@ -73,7 +73,7 @@ func isValidAddress(addr *net.IPOrDomain) bool {
|
||||
}
|
||||
|
||||
a := addr.AsAddress()
|
||||
return a != net.AnyIP
|
||||
return a != net.AnyIP && a != net.AnyIPv6
|
||||
}
|
||||
|
||||
// Process implements proxy.Outbound.
|
||||
@@ -418,7 +418,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
destAddr, _ := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
|
||||
destAddr := b.UDP.RawNetAddr()
|
||||
if destAddr == nil {
|
||||
b.Release()
|
||||
continue
|
||||
|
||||
@@ -18,11 +18,12 @@ import (
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
http_proto "github.com/xtls/xray-core/common/protocol/http"
|
||||
"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/features/policy"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -95,6 +96,9 @@ func (s *Server) ProcessWithFirstbyte(ctx context.Context, network net.Network,
|
||||
inbound.User = &protocol.MemoryUser{
|
||||
Level: s.config.UserLevel,
|
||||
}
|
||||
if !proxy.IsRAWTransport(conn) {
|
||||
inbound.CanSpliceCopy = 3
|
||||
}
|
||||
var reader *bufio.Reader
|
||||
if len(firstbyte) > 0 {
|
||||
readerWithoutFirstbyte := bufio.NewReaderSize(readerOnly{conn}, buf.Size)
|
||||
@@ -169,62 +173,31 @@ Start:
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
_, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
|
||||
if err != nil {
|
||||
return errors.New("failed to write back OK response").Base(err)
|
||||
}
|
||||
|
||||
plcy := s.policy()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
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())))
|
||||
reader := buf.NewReader(conn)
|
||||
if buffer.Buffered() > 0 {
|
||||
payload, err := buf.ReadFrom(io.LimitReader(buffer, int64(buffer.Buffered())))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := link.Writer.WriteMultiBuffer(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
reader = nil
|
||||
reader = &buf.BufferedReader{Reader: reader, Buffer: payload}
|
||||
buffer = nil
|
||||
}
|
||||
|
||||
requestDone := func() error {
|
||||
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
|
||||
|
||||
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
|
||||
}
|
||||
|
||||
responseDone := func() error {
|
||||
if inbound.CanSpliceCopy == 2 {
|
||||
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
|
||||
}
|
||||
|
||||
closeWriter := task.OnSuccess(requestDone, task.Close(link.Writer))
|
||||
if err := task.Run(ctx, closeWriter, responseDone); err != nil {
|
||||
common.Interrupt(link.Reader)
|
||||
common.Interrupt(link.Writer)
|
||||
return errors.New("connection ends").Base(err)
|
||||
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
|
||||
Reader: &buf.TimeoutWrapperReader{Reader: reader},
|
||||
Writer: buf.NewWriter(conn)},
|
||||
); err != nil {
|
||||
return errors.New("failed to dispatch request").Base(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"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/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/reality"
|
||||
@@ -524,24 +525,33 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
|
||||
}
|
||||
}
|
||||
|
||||
// UnwrapRawConn support unwrap stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
|
||||
// UnwrapRawConn support unwrap encryption, 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) {
|
||||
var readCounter, writerCounter stats.Counter
|
||||
if conn != nil {
|
||||
statConn, ok := conn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
isEncryption := false
|
||||
if commonConn, ok := conn.(*encryption.CommonConn); 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
|
||||
readCounter = statConn.ReadCounter
|
||||
writerCounter = statConn.WriteCounter
|
||||
}
|
||||
if xc, ok := conn.(*tls.Conn); ok {
|
||||
conn = xc.NetConn()
|
||||
} else if utlsConn, ok := conn.(*tls.UConn); ok {
|
||||
conn = utlsConn.NetConn()
|
||||
} else if realityConn, ok := conn.(*reality.Conn); ok {
|
||||
conn = realityConn.NetConn()
|
||||
} else if realityUConn, ok := conn.(*reality.UConn); ok {
|
||||
conn = realityUConn.NetConn()
|
||||
if !isEncryption { // avoids double penetration
|
||||
if xc, ok := conn.(*tls.Conn); ok {
|
||||
conn = xc.NetConn()
|
||||
} else if utlsConn, ok := conn.(*tls.UConn); ok {
|
||||
conn = utlsConn.NetConn()
|
||||
} else if realityConn, ok := conn.(*reality.Conn); ok {
|
||||
conn = realityConn.NetConn()
|
||||
} else if realityUConn, ok := conn.(*reality.UConn); ok {
|
||||
conn = realityUConn.NetConn()
|
||||
}
|
||||
}
|
||||
if pc, ok := conn.(*proxyproto.Conn); ok {
|
||||
conn = pc.Raw()
|
||||
@@ -626,15 +636,29 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Cause(err) == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {
|
||||
errors.LogInfo(ctx, "CopyRawConn readv")
|
||||
errors.LogInfo(ctx, "CopyRawConn (maybe) readv")
|
||||
if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
|
||||
return errors.New("failed to process response").Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsRAWTransport(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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
|
||||
request := protocol.RequestHeaderFromContext(ctx)
|
||||
payload := packet.Payload
|
||||
if request == nil {
|
||||
payload.Release()
|
||||
return
|
||||
}
|
||||
|
||||
payload := packet.Payload
|
||||
|
||||
if payload.UDP != nil {
|
||||
request = &protocol.RequestHeader{
|
||||
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")
|
||||
return
|
||||
}
|
||||
defer data.Release()
|
||||
|
||||
conn.Write(data.Bytes())
|
||||
data.Release()
|
||||
})
|
||||
defer udpServer.RemoveRay()
|
||||
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
udp_proto "github.com/xtls/xray-core/common/protocol/udp"
|
||||
"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/features/policy"
|
||||
"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/transport"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/udp"
|
||||
)
|
||||
@@ -75,6 +75,9 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
||||
inbound.User = &protocol.MemoryUser{
|
||||
Level: s.config.UserLevel,
|
||||
}
|
||||
if !proxy.IsRAWTransport(conn) {
|
||||
inbound.CanSpliceCopy = 3
|
||||
}
|
||||
|
||||
switch network {
|
||||
case net.Network_TCP:
|
||||
@@ -154,8 +157,16 @@ func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatche
|
||||
Reason: "",
|
||||
})
|
||||
}
|
||||
|
||||
return s.transport(ctx, reader, conn, dest, dispatcher, inbound)
|
||||
if inbound.CanSpliceCopy == 2 {
|
||||
inbound.CanSpliceCopy = 1
|
||||
}
|
||||
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{
|
||||
Reader: &buf.TimeoutWrapperReader{Reader: reader},
|
||||
Writer: buf.NewWriter(conn)},
|
||||
); err != nil {
|
||||
return errors.New("failed to dispatch request").Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if request.Command == protocol.RequestCommandUDP {
|
||||
@@ -174,52 +185,6 @@ func (*Server) handleUDP(c io.Reader) error {
|
||||
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 {
|
||||
if s.udpFilter != nil && !s.udpFilter.Check(conn.RemoteAddr()) {
|
||||
errors.LogDebug(ctx, "Unauthorized UDP access from ", conn.RemoteAddr().String())
|
||||
@@ -231,6 +196,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
|
||||
|
||||
request := protocol.RequestHeaderFromContext(ctx)
|
||||
if request == nil {
|
||||
payload.Release()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -249,9 +215,9 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
|
||||
errors.LogWarningInner(ctx, err, "failed to write UDP response")
|
||||
return
|
||||
}
|
||||
defer udpMessage.Release()
|
||||
|
||||
conn.Write(udpMessage.Bytes())
|
||||
udpMessage.Release()
|
||||
})
|
||||
defer udpServer.RemoveRay()
|
||||
|
||||
@@ -259,7 +225,6 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
|
||||
if inbound != nil && inbound.Source.IsValid() {
|
||||
errors.LogInfo(ctx, "client UDP connection from ", inbound.Source)
|
||||
}
|
||||
inbound.CanSpliceCopy = 1
|
||||
|
||||
var dest *net.Destination
|
||||
|
||||
|
||||
@@ -113,9 +113,11 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
target = b.UDP
|
||||
}
|
||||
if _, err := w.writePacket(b.Bytes(), *target); err != nil {
|
||||
b.Release()
|
||||
buf.ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
b.Release()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
||||
sessionPolicy = s.policyManager.ForLevel(user.Level)
|
||||
|
||||
if destination.Network == net.Network_UDP { // handle udp request
|
||||
return s.handleUDPPayload(ctx, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher)
|
||||
return s.handleUDPPayload(ctx, sessionPolicy, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher)
|
||||
}
|
||||
|
||||
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
@@ -248,7 +248,12 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
|
||||
return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher)
|
||||
}
|
||||
|
||||
func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error {
|
||||
func (s *Server) handleUDPPayload(ctx context.Context, sessionPolicy policy.Session, 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)
|
||||
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
|
||||
udpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
|
||||
udpPayload := packet.Payload
|
||||
if udpPayload.UDP == nil {
|
||||
@@ -257,6 +262,9 @@ func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReade
|
||||
|
||||
if err := clientWriter.WriteMultiBuffer(buf.MultiBuffer{udpPayload}); err != nil {
|
||||
errors.LogWarningInner(ctx, err, "failed to write response")
|
||||
cancel()
|
||||
} else {
|
||||
timer.Update()
|
||||
}
|
||||
})
|
||||
defer udpServer.RemoveRay()
|
||||
@@ -266,47 +274,56 @@ func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReade
|
||||
|
||||
var dest *net.Destination
|
||||
|
||||
for {
|
||||
select {
|
||||
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)
|
||||
}
|
||||
requestDone := func() error {
|
||||
for {
|
||||
select {
|
||||
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
|
||||
}
|
||||
|
||||
mb2, b := buf.SplitFirst(mb)
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
destination := *b.UDP
|
||||
mb2, b := buf.SplitFirst(mb)
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
timer.Update()
|
||||
destination := *b.UDP
|
||||
|
||||
currentPacketCtx := ctx
|
||||
if inbound.Source.IsValid() {
|
||||
currentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
From: inbound.Source,
|
||||
To: destination,
|
||||
Status: log.AccessAccepted,
|
||||
Reason: "",
|
||||
Email: user.Email,
|
||||
})
|
||||
}
|
||||
errors.LogInfo(ctx, "tunnelling request to ", destination)
|
||||
currentPacketCtx := ctx
|
||||
if inbound.Source.IsValid() {
|
||||
currentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
|
||||
From: inbound.Source,
|
||||
To: destination,
|
||||
Status: log.AccessAccepted,
|
||||
Reason: "",
|
||||
Email: user.Email,
|
||||
})
|
||||
}
|
||||
errors.LogInfo(ctx, "tunnelling request to ", destination)
|
||||
|
||||
if !s.cone || dest == nil {
|
||||
dest = &destination
|
||||
}
|
||||
if !s.cone || dest == nil {
|
||||
dest = &destination
|
||||
}
|
||||
|
||||
udpServer.Dispatch(currentPacketCtx, *dest, b) // first packet
|
||||
for _, payload := range mb2 {
|
||||
udpServer.Dispatch(currentPacketCtx, *dest, payload)
|
||||
udpServer.Dispatch(currentPacketCtx, *dest, b) // first packet
|
||||
for _, payload := range mb2 {
|
||||
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,
|
||||
|
||||
@@ -18,6 +18,8 @@ func (a *Account) AsAccount() (protocol.Account, error) {
|
||||
ID: protocol.NewID(id),
|
||||
Flow: a.Flow, // needs parser here?
|
||||
Encryption: a.Encryption, // needs parser here?
|
||||
XorMode: a.XorMode,
|
||||
Seconds: a.Seconds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -27,8 +29,10 @@ type MemoryAccount struct {
|
||||
ID *protocol.ID
|
||||
// Flow of the account. May be "xtls-rprx-vision".
|
||||
Flow string
|
||||
// Encryption of the account. Used for client connections, and only accepts "none" for now.
|
||||
|
||||
Encryption string
|
||||
XorMode uint32
|
||||
Seconds uint32
|
||||
}
|
||||
|
||||
// Equals implements protocol.Account.Equals().
|
||||
@@ -45,5 +49,7 @@ func (a *MemoryAccount) ToProto() proto.Message {
|
||||
Id: a.ID.String(),
|
||||
Flow: a.Flow,
|
||||
Encryption: a.Encryption,
|
||||
XorMode: a.XorMode,
|
||||
Seconds: a.Seconds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,10 @@ type Account struct {
|
||||
// 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"`
|
||||
// Flow settings. May be "xtls-rprx-vision".
|
||||
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.
|
||||
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,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"`
|
||||
}
|
||||
|
||||
func (x *Account) Reset() {
|
||||
@@ -84,23 +85,40 @@ func (x *Account) GetEncryption() string {
|
||||
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
|
||||
}
|
||||
|
||||
var File_proxy_vless_account_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_proxy_vless_account_proto_rawDesc = []byte{
|
||||
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,
|
||||
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x4d, 0x0a,
|
||||
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a,
|
||||
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14,
|
||||
0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
|
||||
0x6c, 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,
|
||||
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x81, 0x01,
|
||||
0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f,
|
||||
0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a,
|
||||
0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
|
||||
0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e,
|
||||
0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64,
|
||||
0x73, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 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 (
|
||||
|
||||
@@ -11,6 +11,8 @@ message Account {
|
||||
string id = 1;
|
||||
// Flow settings. May be "xtls-rprx-vision".
|
||||
string flow = 2;
|
||||
// Encryption settings. Only applies to client side, and only accepts "none" for now.
|
||||
|
||||
string encryption = 3;
|
||||
uint32 xorMode = 4;
|
||||
uint32 seconds = 5;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A
|
||||
}
|
||||
|
||||
// XtlsRead filter and read xtls protocol
|
||||
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 {
|
||||
func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, peerCache *[]byte, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
|
||||
err := func() error {
|
||||
for {
|
||||
if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
|
||||
@@ -194,15 +194,21 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer,
|
||||
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)
|
||||
// XTLS Vision processes struct Encryption Conn's peerCache or TLS Conn's input and rawInput
|
||||
if peerCache != nil {
|
||||
if len(*peerCache) != 0 {
|
||||
buffer = buf.MergeBytes(buffer, *peerCache)
|
||||
}
|
||||
}
|
||||
if rawInputBuffer, err := buf.ReadFrom(rawInput); err == nil {
|
||||
if !rawInputBuffer.IsEmpty() {
|
||||
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
203
proxy/vless/encryption/client.go
Normal file
203
proxy/vless/encryption/client.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/mlkem"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"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
|
||||
|
||||
RWLock sync.RWMutex
|
||||
Expire time.Time
|
||||
PfsKey []byte
|
||||
Ticket []byte
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) {
|
||||
if i.NfsPKeys != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
}
|
||||
l := len(nfsPKeysBytes)
|
||||
if l == 0 {
|
||||
err = errors.New("empty nfsPKeysBytes")
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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 := int(crypto.RandBetween(100, 1000))
|
||||
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)
|
||||
|
||||
if _, err := conn.Write(clientHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
|
||||
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
|
||||
}
|
||||
219
proxy/vless/encryption/common.go
Normal file
219
proxy/vless/encryption/common.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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
|
||||
PeerInBytes []byte
|
||||
PeerCache []byte
|
||||
}
|
||||
|
||||
func NewCommonConn(conn net.Conn, useAES bool) *CommonConn {
|
||||
return &CommonConn{
|
||||
Conn: conn,
|
||||
UseAES: useAES,
|
||||
PeerInBytes: make([]byte, 5+17000), // no need to use sync.Pool, because we are always reading
|
||||
}
|
||||
}
|
||||
|
||||
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 len(c.PeerCache) > 0 {
|
||||
n := copy(b, c.PeerCache)
|
||||
c.PeerCache = c.PeerCache[n:]
|
||||
return n, nil
|
||||
}
|
||||
peerHeader := c.PeerInBytes[:5]
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
l, err := DecodeHeader(c.PeerInBytes[:5]) // 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
|
||||
peerData := c.PeerInBytes[5 : 5+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(c.PeerInBytes[:5+l], 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.PeerCache = 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
|
||||
}
|
||||
287
proxy/vless/encryption/server.go
Normal file
287
proxy/vless/encryption/server.go
Normal file
@@ -0,0 +1,287 @@
|
||||
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 {
|
||||
Expire time.Time
|
||||
PfsKey []byte
|
||||
NfsKeys sync.Map
|
||||
}
|
||||
|
||||
type ServerInstance struct {
|
||||
NfsSKeys []any
|
||||
NfsPKeysBytes [][]byte
|
||||
Hash32s [][32]byte
|
||||
RelaysLength int
|
||||
XorMode uint32
|
||||
Seconds uint32
|
||||
|
||||
RWLock sync.RWMutex
|
||||
Sessions map[[16]byte]*ServerSession
|
||||
Closed bool
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) {
|
||||
if i.NfsSKeys != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
}
|
||||
l := len(nfsSKeysBytes)
|
||||
if l == 0 {
|
||||
err = errors.New("empty nfsSKeysBytes")
|
||||
return
|
||||
}
|
||||
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
|
||||
if seconds > 0 {
|
||||
i.Seconds = seconds
|
||||
i.Sessions = make(map[[16]byte]*ServerSession)
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Minute)
|
||||
i.RWLock.Lock()
|
||||
if i.Closed {
|
||||
i.RWLock.Unlock()
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
for ticket, session := range i.Sessions {
|
||||
if now.After(session.Expire) {
|
||||
delete(i.Sessions, ticket)
|
||||
}
|
||||
}
|
||||
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) (*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
|
||||
}
|
||||
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 elligator, because we have PSK :)
|
||||
}
|
||||
if k, ok := k.(*ecdh.PrivateKey); ok {
|
||||
publicKey, err := ecdh.X25519().NewPublicKey(relays[:index])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
length := DecodeLength(decryptedLength)
|
||||
|
||||
if length == 32 {
|
||||
if i.Seconds == 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(1268, 2268)) // 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 := make([]byte, 16)
|
||||
rand.Read(ticket)
|
||||
copy(ticket, EncodeLength(int(i.Seconds*4/5)))
|
||||
|
||||
pfsKeyExchangeLength := 1088 + 32 + 16
|
||||
encryptedTicketLength := 32
|
||||
paddingLength := int(crypto.RandBetween(100, 1000))
|
||||
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)
|
||||
|
||||
if _, err := conn.Write(serverHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
|
||||
if i.Seconds > 0 {
|
||||
i.RWLock.Lock()
|
||||
i.Sessions[[16]byte(ticket)] = &ServerSession{
|
||||
Expire: time.Now().Add(time.Duration(i.Seconds) * time.Second),
|
||||
PfsKey: pfsKey,
|
||||
}
|
||||
i.RWLock.Unlock()
|
||||
}
|
||||
|
||||
// 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
proxy/vless/encryption/xor.go
Normal file
93
proxy/vless/encryption/xor.go
Normal file
@@ -0,0 +1,93 @@
|
||||
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
|
||||
}
|
||||
@@ -111,11 +111,11 @@ type Config struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
|
||||
// Decryption settings. Only applies to server side, and only accepts "none"
|
||||
// for now.
|
||||
Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"`
|
||||
Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,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 string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,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"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
@@ -155,6 +155,13 @@ func (x *Config) GetClients() []*protocol.User {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetFallbacks() []*Fallback {
|
||||
if x != nil {
|
||||
return x.Fallbacks
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetDecryption() string {
|
||||
if x != nil {
|
||||
return x.Decryption
|
||||
@@ -162,11 +169,18 @@ func (x *Config) GetDecryption() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetFallbacks() []*Fallback {
|
||||
func (x *Config) GetXorMode() uint32 {
|
||||
if x != nil {
|
||||
return x.Fallbacks
|
||||
return x.XorMode
|
||||
}
|
||||
return nil
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetSeconds() uint32 {
|
||||
if x != nil {
|
||||
return x.Seconds
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor
|
||||
@@ -185,25 +199,28 @@ 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,
|
||||
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,
|
||||
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xa0, 0x01,
|
||||
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xd4, 0x01,
|
||||
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,
|
||||
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, 0x1e,
|
||||
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,
|
||||
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x40,
|
||||
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
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, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73,
|
||||
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,
|
||||
0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65,
|
||||
0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63,
|
||||
0x6f, 0x6e, 0x64, 0x73, 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 (
|
||||
|
||||
@@ -19,8 +19,9 @@ message Fallback {
|
||||
|
||||
message Config {
|
||||
repeated xray.common.protocol.User clients = 1;
|
||||
// Decryption settings. Only applies to server side, and only accepts "none"
|
||||
// for now.
|
||||
string decryption = 2;
|
||||
repeated Fallback fallbacks = 3;
|
||||
repeated Fallback fallbacks = 2;
|
||||
|
||||
string decryption = 3;
|
||||
uint32 xorMode = 4;
|
||||
uint32 seconds = 5;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
gotls "crypto/tls"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
"github.com/xtls/xray-core/proxy/vless/encoding"
|
||||
"github.com/xtls/xray-core/proxy/vless/encryption"
|
||||
"github.com/xtls/xray-core/transport/internet/reality"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
@@ -67,6 +69,7 @@ type Handler struct {
|
||||
policyManager policy.Manager
|
||||
validator vless.Validator
|
||||
dns dns.Client
|
||||
decryption *encryption.ServerInstance
|
||||
fallbacks map[string]map[string]map[string]*Fallback // or nil
|
||||
// regexps map[string]*regexp.Regexp // or nil
|
||||
}
|
||||
@@ -81,6 +84,19 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
|
||||
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.Seconds); err != nil {
|
||||
return nil, errors.New("failed to use decryption").Base(err).AtError()
|
||||
}
|
||||
}
|
||||
|
||||
if config.Fallbacks != nil {
|
||||
handler.fallbacks = make(map[string]map[string]map[string]*Fallback)
|
||||
// handler.regexps = make(map[string]*regexp.Regexp)
|
||||
@@ -159,6 +175,9 @@ func isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool {
|
||||
|
||||
// Close implements common.Closable.Close().
|
||||
func (h *Handler) Close() error {
|
||||
if h.decryption != nil {
|
||||
h.decryption.Close()
|
||||
}
|
||||
return errors.Combine(common.Close(h.validator))
|
||||
}
|
||||
|
||||
@@ -199,6 +218,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
iConn = statConn.Connection
|
||||
}
|
||||
|
||||
if h.decryption != nil {
|
||||
var err error
|
||||
connection, err = h.decryption.Handshake(connection)
|
||||
if err != nil {
|
||||
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
|
||||
}
|
||||
}
|
||||
|
||||
sessionPolicy := h.policyManager.ForLevel(0)
|
||||
if err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
|
||||
return errors.New("unable to set read deadline").Base(err).AtWarning()
|
||||
@@ -464,6 +491,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
// Flow: requestAddons.Flow,
|
||||
}
|
||||
|
||||
var peerCache *[]byte
|
||||
var input *bytes.Reader
|
||||
var rawInput *bytes.Buffer
|
||||
switch requestAddons.Flow {
|
||||
@@ -476,6 +504,13 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
case protocol.RequestCommandMux:
|
||||
fallthrough // we will break Mux connections that contain TCP requests
|
||||
case protocol.RequestCommandTCP:
|
||||
if serverConn, ok := connection.(*encryption.CommonConn); ok {
|
||||
peerCache = &serverConn.PeerCache
|
||||
if _, ok := serverConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) {
|
||||
inbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice
|
||||
}
|
||||
break
|
||||
}
|
||||
var t reflect.Type
|
||||
var p uintptr
|
||||
if tlsConn, ok := iConn.(*tls.Conn); ok {
|
||||
@@ -544,7 +579,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
||||
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)
|
||||
err = encoding.XtlsRead(clientReader, serverWriter, timer, connection, peerCache, input, rawInput, trafficState, nil, true, ctx1)
|
||||
} else {
|
||||
// from clientReader.ReadMultiBuffer to serverWriter.WriteMultiBuffer
|
||||
err = buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
gotls "crypto/tls"
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@@ -24,6 +26,7 @@ import (
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
"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"
|
||||
"github.com/xtls/xray-core/transport/internet/reality"
|
||||
@@ -43,6 +46,7 @@ type Handler struct {
|
||||
serverPicker protocol.ServerPicker
|
||||
policyManager policy.Manager
|
||||
cone bool
|
||||
encryption *encryption.ClientInstance
|
||||
}
|
||||
|
||||
// New creates a new VLess outbound handler.
|
||||
@@ -64,6 +68,20 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
|
||||
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); err != nil {
|
||||
return nil, errors.New("failed to use encryption").Base(err).AtError()
|
||||
}
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
@@ -98,6 +116,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
target := ob.Target
|
||||
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr())
|
||||
|
||||
if h.encryption != nil {
|
||||
var err error
|
||||
conn, err = h.encryption.Handshake(conn)
|
||||
if err != nil {
|
||||
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
|
||||
}
|
||||
}
|
||||
|
||||
command := protocol.RequestCommandTCP
|
||||
if target.Network == net.Network_UDP {
|
||||
command = protocol.RequestCommandUDP
|
||||
@@ -120,6 +146,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
Flow: account.Flow,
|
||||
}
|
||||
|
||||
var peerCache *[]byte
|
||||
var input *bytes.Reader
|
||||
var rawInput *bytes.Buffer
|
||||
allowUDP443 := false
|
||||
@@ -138,6 +165,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
case protocol.RequestCommandMux:
|
||||
fallthrough // let server break Mux connections that contain TCP requests
|
||||
case protocol.RequestCommandTCP:
|
||||
if clientConn, ok := conn.(*encryption.CommonConn); ok {
|
||||
peerCache = &clientConn.PeerCache
|
||||
if _, ok := clientConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransport(iConn) {
|
||||
ob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport can not use Linux Splice
|
||||
}
|
||||
break
|
||||
}
|
||||
var t reflect.Type
|
||||
var p uintptr
|
||||
if tlsConn, ok := iConn.(*tls.Conn); ok {
|
||||
@@ -272,7 +306,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
}
|
||||
|
||||
if requestAddons.Flow == vless.XRV {
|
||||
err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, input, rawInput, trafficState, ob, false, ctx)
|
||||
err = encoding.XtlsRead(serverReader, clientWriter, timer, conn, peerCache, input, rawInput, trafficState, ob, false, ctx)
|
||||
} else {
|
||||
// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer
|
||||
err = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))
|
||||
|
||||
@@ -129,7 +129,8 @@ func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = h.bind.Close()
|
||||
h.bind.Close()
|
||||
h.bind = nil
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ func TestZeroBuffer(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
|
||||
@@ -182,7 +182,7 @@ func TestReverseProxy(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 32; i++ {
|
||||
for range 32 {
|
||||
errg.Go(testTCPConn(externalPort, 10240*1024, time.Second*40))
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ func TestReverseProxyLongRunning(t *testing.T) {
|
||||
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
for i := 0; i < 4096; i++ {
|
||||
for range 4096 {
|
||||
if err := testTCPConn(externalPort, 1024, time.Second*20)(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ func testShadowsocks2022Tcp(t *testing.T, method string, password string) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errGroup errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
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)
|
||||
|
||||
var errGroup errgroup.Group
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 3 {
|
||||
errGroup.Go(testUDPConn(udpClientPort, 1024, time.Second*5))
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestShadowsocksChaCha20Poly1305TCP(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errGroup errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
|
||||
}
|
||||
if err := errGroup.Wait(); err != nil {
|
||||
@@ -192,7 +192,7 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errGroup errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errGroup errgroup.Group
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 3 {
|
||||
errGroup.Go(testUDPConn(clientPort, 1024, time.Second*5))
|
||||
}
|
||||
if err := errGroup.Wait(); err != nil {
|
||||
@@ -391,7 +391,7 @@ func TestShadowsocksAES128GCMUDPMux(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errGroup errgroup.Group
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 3 {
|
||||
errGroup.Go(testUDPConn(clientPort, 1024, time.Second*5))
|
||||
}
|
||||
if err := errGroup.Wait(); err != nil {
|
||||
@@ -477,7 +477,7 @@ func TestShadowsocksNone(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errGroup errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ func TestAutoIssuingCertificate(t *testing.T) {
|
||||
common.Must(err)
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
if err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -449,7 +449,7 @@ func TestTLSOverWebSocket(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -565,7 +565,7 @@ func TestGRPC(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -681,7 +681,7 @@ func TestGRPCMultiMode(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
|
||||
@@ -117,7 +117,7 @@ func TestVless(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -239,7 +239,7 @@ func TestVlessTls(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -363,7 +363,7 @@ func TestVlessXtlsVision(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -502,7 +502,7 @@ func TestVlessXtlsVisionReality(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 1; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
|
||||
@@ -260,7 +260,7 @@ func TestVMessGCM(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
|
||||
}
|
||||
|
||||
@@ -366,7 +366,7 @@ func TestVMessGCMReadv(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -465,7 +465,7 @@ func TestVMessGCMUDP(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testUDPConn(clientPort, 1024, time.Second*5))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -564,7 +564,7 @@ func TestVMessChacha20(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))
|
||||
}
|
||||
|
||||
@@ -664,7 +664,7 @@ func TestVMessNone(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -771,7 +771,7 @@ func TestVMessKCP(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024, time.Minute*2))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -915,7 +915,7 @@ func TestVMessKCPLarge(t *testing.T) {
|
||||
common.Must(err)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 513*1024, time.Minute*5))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -1026,7 +1026,7 @@ func TestVMessGCMMux(t *testing.T) {
|
||||
|
||||
for range "abcd" {
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 16; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 10240, time.Second*20))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -1152,7 +1152,7 @@ func TestVMessGCMMuxUDP(t *testing.T) {
|
||||
|
||||
for range "ab" {
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024, time.Second*10))
|
||||
errg.Go(testUDPConn(clientUDPPort, 1024, time.Second*10))
|
||||
}
|
||||
@@ -1259,7 +1259,7 @@ func TestVMessZero(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))
|
||||
}
|
||||
if err := errg.Wait(); err != nil {
|
||||
@@ -1361,7 +1361,7 @@ func TestVMessGCMLengthAuth(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
|
||||
}
|
||||
|
||||
@@ -1465,7 +1465,7 @@ func TestVMessGCMLengthAuthPlusNoTerminationSignal(t *testing.T) {
|
||||
defer CloseAllServers(servers)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 3 {
|
||||
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,24 @@ type ResponseCallback func(ctx context.Context, packet *udp.Packet)
|
||||
|
||||
type connEntry struct {
|
||||
link *transport.Link
|
||||
timer signal.ActivityUpdater
|
||||
timer *signal.ActivityTimer
|
||||
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 {
|
||||
@@ -32,6 +48,7 @@ type Dispatcher struct {
|
||||
dispatcher routing.Dispatcher
|
||||
callback ResponseCallback
|
||||
callClose func() error
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Dispatcher {
|
||||
@@ -44,13 +61,9 @@ func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Di
|
||||
func (v *Dispatcher) RemoveRay() {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
v.removeRay()
|
||||
}
|
||||
|
||||
func (v *Dispatcher) removeRay() {
|
||||
v.closed = true
|
||||
if v.conn != nil {
|
||||
common.Interrupt(v.conn.link.Reader)
|
||||
common.Close(v.conn.link.Writer)
|
||||
v.conn.Close()
|
||||
v.conn = nil
|
||||
}
|
||||
}
|
||||
@@ -59,35 +72,34 @@ func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) (*
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
if v.closed {
|
||||
return nil, errors.New("dispatcher is closed")
|
||||
}
|
||||
|
||||
if v.conn != nil {
|
||||
return v.conn, nil
|
||||
if v.conn.closed {
|
||||
v.conn = nil
|
||||
} else {
|
||||
return v.conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
errors.LogInfo(ctx, "establishing new connection for ", dest)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, errors.New("failed to dispatch request to ", dest).Base(err)
|
||||
}
|
||||
|
||||
*entry = connEntry{
|
||||
entry := &connEntry{
|
||||
link: link,
|
||||
timer: timer,
|
||||
cancel: removeRay,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
entry.timer = signal.CancelAfterInactivity(ctx, entry.terminate, time.Minute)
|
||||
v.conn = entry
|
||||
go handleInput(ctx, entry, dest, v.callback, v.callClose)
|
||||
return entry, nil
|
||||
@@ -106,7 +118,7 @@ func (v *Dispatcher) Dispatch(ctx context.Context, destination net.Destination,
|
||||
if outputStream != nil {
|
||||
if err := outputStream.WriteMultiBuffer(buf.MultiBuffer{payload}); err != nil {
|
||||
errors.LogInfoInner(ctx, err, "failed to write first UDP payload")
|
||||
conn.cancel()
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -114,7 +126,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) {
|
||||
defer func() {
|
||||
conn.cancel()
|
||||
conn.Close()
|
||||
if callClose != nil {
|
||||
callClose()
|
||||
}
|
||||
|
||||
@@ -200,16 +200,19 @@ func (p *pipe) Interrupt() {
|
||||
p.Lock()
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
p.state = errord
|
||||
|
||||
if !p.data.IsEmpty() {
|
||||
buf.ReleaseMulti(p.data)
|
||||
p.data = nil
|
||||
}
|
||||
|
||||
common.Must(p.done.Close())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user