1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2025-07-05 16:38:17 -04:00

Add TLSMirror looks like TLS censorship resistant transport protocol as a developer preview transport (#3437)

* Add tlsmirror server processing routine

* Add tlsmirror server processing routine: generated

* Add tlsmirror server handshake capture

* it runs version~

* add draining copy for handshake

* refactor out base tls mirror connection

* tls mirror server side base

* add random field extraction

* add tls like encryption

* add tls like encryption (generated)

* add server side implementation for tlsmirror

* apply coding style: tlsmirror

* fix typo in mirrortls mirror crypto

* add client initial implementation for tls mirror

* add traffic generator implementation for tlsmirror

* add client processing of traffic generator originated traffic

* add embedded traffic generator support to mirrortls client

* override security setting of traffic generator if appropriate

* override security setting of traffic generator if appropriate

* apply request wait time for traffic generator

* add unsafe keyword required for linkname

* fix outbound manager registration for traffic ingress at tlsmirror client

* initial works at sticking packets together

* fix traffic generator's traffic goto logic

* fix get client and server random

* fix applying primary key

* fix log error handling for handshake random retrieval

* fix nonce generation and key derivation logic

* fix: add readPipe channel to client and server connection handlers

* fix: use detached context for persistent mirror TLS dialer

* fix: ensure proper closure of connections on context cancellation

* fix: proper detection of traffic generator originated connection

wait for connection ready before sending payload

* fix coding style
This commit is contained in:
Xiaokang Wang (Shelikhoo) 2025-07-03 11:33:16 +01:00 committed by GitHub
parent 248cba4e43
commit b1ef737d48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 2530 additions and 3 deletions

View File

@ -113,7 +113,7 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
}
func (w *tcpWorker) Start() error {
ctx := context.Background()
ctx := w.ctx
proxyEnvironment := envctx.EnvironmentFromContext(w.ctx).(environment.ProxyEnvironment)
transportEnvironment, err := proxyEnvironment.NarrowScopeToTransport("transport")
if err != nil {

View File

@ -229,7 +229,7 @@ func (t *transportEnvImpl) Listener() internet.SystemListener {
}
func (t *transportEnvImpl) OutboundDialer() tagged.DialFunc {
panic("implement me")
return tagged.Dialer
}
func (t *transportEnvImpl) TransientStorage() storage.ScopedTransientStorage {

View File

@ -256,4 +256,4 @@ func TestSniffFakeQUICPacketWithTooShortData(t *testing.T) {
if err == nil {
t.Error("failed")
}
}
}

View File

@ -87,6 +87,8 @@ import (
_ "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
_ "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/server"
// Transport headers
_ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
_ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/noop"

View File

@ -0,0 +1,46 @@
package tlsmirror
import (
"context"
"github.com/v2fly/v2ray-core/v5/common"
)
type TLSRecord struct {
RecordType byte
LegacyProtocolVersion [2]byte
RecordLength uint16
Fragment []byte
}
type RecordReader interface {
ReadNextRecord(rejectProfile PartialTLSRecordRejectProfile) (*TLSRecord, error)
}
type RecordWriter interface {
WriteRecord(record *TLSRecord) error
}
type Peeker interface {
Peek(n int) ([]byte, error)
}
type PartialTLSRecordRejectProfile interface {
TestIfReject(record *TLSRecord, readyFields int) error
}
type MessageHook func(message *TLSRecord) (drop bool, ok error)
type InsertableTLSConn interface {
common.Closable
GetHandshakeRandom() ([]byte, []byte, error)
InsertC2SMessage(message *TLSRecord) error
InsertS2CMessage(message *TLSRecord) error
}
const TrafficGeneratorManagedConnectionContextKey = "TrafficGeneratorManagedConnection-ku63HMMD-kduCPhr8-DN4y6WEa"
type TrafficGeneratorManagedConnection interface {
RecallTrafficGenerator() error
WaitConnectionReady() context.Context
}

View File

@ -0,0 +1,3 @@
package mirrorbase
//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen

View File

@ -0,0 +1,342 @@
package mirrorbase
import (
"bufio"
"bytes"
"context"
"io"
"net"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorcommon"
)
// NewMirroredTLSConn creates a new mirrored TLS connection.
// No stable interface
func NewMirroredTLSConn(ctx context.Context, clientConn net.Conn, serverConn net.Conn, onC2SMessage, onS2CMessage tlsmirror.MessageHook, closable common.Closable) tlsmirror.InsertableTLSConn {
c := &conn{
ctx: ctx,
clientConn: clientConn,
serverConn: serverConn,
c2sInsert: make(chan *tlsmirror.TLSRecord, 100),
s2cInsert: make(chan *tlsmirror.TLSRecord, 100),
OnC2SMessage: onC2SMessage,
OnS2CMessage: onS2CMessage,
}
c.ctx, c.done = context.WithCancel(ctx)
go c.c2sWorker()
go c.s2cWorker()
go func() {
<-c.ctx.Done()
if closable != nil {
closable.Close()
}
c.clientConn.Close()
c.serverConn.Close()
}()
return c
}
type conn struct {
ctx context.Context
done context.CancelFunc
clientConn net.Conn
serverConn net.Conn
OnC2SMessage tlsmirror.MessageHook
OnS2CMessage tlsmirror.MessageHook
c2sInsert chan *tlsmirror.TLSRecord
s2cInsert chan *tlsmirror.TLSRecord
isClientRandomReady bool
ClientRandom [32]byte
isServerRandomReady bool
ServerRandom [32]byte
}
func (c *conn) GetHandshakeRandom() ([]byte, []byte, error) {
if !c.isClientRandomReady || !c.isServerRandomReady {
return nil, nil, newError("client random or server random not ready")
}
return c.ClientRandom[:], c.ServerRandom[:], nil
}
func (c *conn) Close() error {
c.done()
return nil
}
func (c *conn) InsertC2SMessage(message *tlsmirror.TLSRecord) error {
duplicatedRecord := mirrorcommon.DuplicateRecord(*message)
c.c2sInsert <- &duplicatedRecord
return nil
}
func (c *conn) InsertS2CMessage(message *tlsmirror.TLSRecord) error {
duplicatedRecord := mirrorcommon.DuplicateRecord(*message)
c.s2cInsert <- &duplicatedRecord
return nil
}
type bufPeeker struct {
buffer []byte
}
func (b *bufPeeker) Peek(n int) ([]byte, error) {
if len(b.buffer) < n {
return nil, newError("not enough data")
}
return b.buffer[:n], nil
}
type readerWithInitialData struct {
initialData []byte
innerReader io.Reader
}
func (r *readerWithInitialData) initialDataDrained() bool {
return len(r.initialData) == 0
}
func (r *readerWithInitialData) Read(p []byte) (n int, err error) {
if len(r.initialData) > 0 {
n = copy(p, r.initialData)
r.initialData = r.initialData[n:]
return n, nil
}
return r.innerReader.Read(p)
}
func (c *conn) c2sWorker() {
c2sHandshake, handshakeReminder, c2sReminderData, err := c.captureHandshake(c.clientConn, c.serverConn)
if err != nil {
c.done()
return
}
_ = c2sHandshake
_ = handshakeReminder
_ = c2sReminderData
serverConnectionWriter := bufio.NewWriter(c.serverConn)
_, err = io.Copy(serverConnectionWriter, bytes.NewReader(handshakeReminder))
if err != nil {
c.done()
newError("failed to copy handshake reminder").Base(err).AtWarning().WriteToLog()
return
}
clientHello, err := mirrorcommon.UnpackTLSClientHello(c2sHandshake.Fragment)
if err != nil {
c.done()
newError("failed to unpack client hello").Base(err).AtWarning().WriteToLog()
return
}
c.ClientRandom = clientHello.ClientRandom
c.isClientRandomReady = true
clientSocketReader := &readerWithInitialData{initialData: c2sReminderData, innerReader: c.clientConn}
clientSocket := bufio.NewReaderSize(clientSocketReader, 65536)
recordReader := mirrorcommon.NewTLSRecordStreamReader(clientSocket)
recordWriter := mirrorcommon.NewTLSRecordStreamWriter(serverConnectionWriter)
if len(c2sReminderData) == 0 {
err := serverConnectionWriter.Flush()
if err != nil {
c.done()
newError("failed to flush server connection writer").Base(err).AtWarning().WriteToLog()
return
}
}
go func() {
for c.ctx.Err() == nil {
record := <-c.c2sInsert
err := recordWriter.WriteRecord(record, false)
if err != nil {
c.done()
newError("failed to write C2S message").Base(err).AtWarning().WriteToLog()
return
}
}
}()
for c.ctx.Err() == nil {
record, err := recordReader.ReadNextRecord()
if err != nil {
drainCopy(c.clientConn, nil, c.serverConn)
c.done()
newError("failed to read TLS record").Base(err).AtWarning().WriteToLog()
return
}
if c.OnC2SMessage != nil {
drop, err := c.OnC2SMessage(record)
if err != nil {
c.done()
newError("failed to process C2S message").Base(err).AtWarning().WriteToLog()
return
}
if drop {
continue
}
}
duplicatedRecord := mirrorcommon.DuplicateRecord(*record)
c.c2sInsert <- &duplicatedRecord
}
}
func (c *conn) s2cWorker() {
// TODO: stick packets together, if they arrived so
s2cHandshake, handshakeReminder, s2cReminderData, err := c.captureHandshake(c.serverConn, c.clientConn)
if err != nil {
c.done()
return
}
_ = s2cHandshake
_ = handshakeReminder
_ = s2cReminderData
clientConnectionWriter := bufio.NewWriter(c.clientConn)
_, err = io.Copy(clientConnectionWriter, bytes.NewReader(handshakeReminder))
if err != nil {
c.done()
newError("failed to copy handshake reminder").Base(err).AtWarning().WriteToLog()
return
}
serverHello, err := mirrorcommon.UnpackTLSServerHello(s2cHandshake.Fragment)
if err != nil {
c.done()
newError("failed to unpack server hello").Base(err).AtWarning().WriteToLog()
return
}
c.ServerRandom = serverHello.ServerRandom
c.isServerRandomReady = true
serverSocketReader := &readerWithInitialData{initialData: s2cReminderData, innerReader: c.serverConn}
serverSocket := bufio.NewReaderSize(serverSocketReader, 65536)
recordReader := mirrorcommon.NewTLSRecordStreamReader(serverSocket)
recordWriter := mirrorcommon.NewTLSRecordStreamWriter(clientConnectionWriter)
if len(s2cReminderData) == 0 {
err := clientConnectionWriter.Flush()
if err != nil {
c.done()
newError("failed to flush client connection writer").Base(err).AtWarning().WriteToLog()
return
}
}
go func() {
for c.ctx.Err() == nil {
record := <-c.s2cInsert
err := recordWriter.WriteRecord(record, false)
if err != nil {
c.done()
newError("failed to write S2C message").Base(err).AtWarning().WriteToLog()
return
}
}
}()
for c.ctx.Err() == nil {
record, err := recordReader.ReadNextRecord()
if err != nil {
drainCopy(c.clientConn, nil, c.serverConn)
c.done()
newError("failed to read TLS record").Base(err).AtWarning().WriteToLog()
return
}
if c.OnS2CMessage != nil {
drop, err := c.OnS2CMessage(record)
if err != nil {
c.done()
newError("failed to process S2C message").Base(err).AtWarning().WriteToLog()
return
}
if drop {
continue
}
}
duplicatedRecord := mirrorcommon.DuplicateRecord(*record)
c.s2cInsert <- &duplicatedRecord
}
}
func drainCopy(dst io.Writer, initData []byte, src io.Reader) {
if initData != nil {
_, err := io.Copy(dst, bytes.NewReader(initData))
if err != nil {
newError("failed to drain copy").Base(err).AtWarning().WriteToLog()
}
}
_, err := io.Copy(dst, src)
if err != nil {
newError("failed to drain copy").Base(err).AtWarning().WriteToLog()
}
}
type rejectionDecisionMaker struct{}
func (r *rejectionDecisionMaker) TestIfReject(record *tlsmirror.TLSRecord, readyFields int) error {
if readyFields >= 1 {
if record.RecordType != mirrorcommon.TLSRecord_RecordType_handshake {
return newError("unexpected record type").AtWarning()
}
}
if readyFields >= 2 {
switch record.LegacyProtocolVersion[0] {
case 0x01:
case 0x02:
case 0x03:
if record.LegacyProtocolVersion[1] > 0x03 {
return newError("unexpected minor protocol version").AtWarning()
}
default:
return newError("unexpected major protocol version").AtWarning()
}
}
return nil
}
func (c *conn) captureHandshake(sourceConn, mirrorConn net.Conn) (handshake tlsmirror.TLSRecord, handshakeReminder, rest []byte, reterr error) {
var readBuffer [65536]byte
var nextRead int
for c.ctx.Err() == nil {
n, err := sourceConn.Read(readBuffer[nextRead:])
if err != nil {
c.done()
reterr = newError("failed to read from source connection").Base(err).AtWarning()
return handshake, nil, nil, reterr
}
handshakeRejectionDecisionMaker := &rejectionDecisionMaker{}
result, tryAgainLen, processed, err := mirrorcommon.PeekTLSRecord(&bufPeeker{buffer: readBuffer[:nextRead+n]}, handshakeRejectionDecisionMaker)
if processed == 0 {
if tryAgainLen == 0 {
// TODO: directly copy
drainCopy(mirrorConn, readBuffer[:nextRead+n], sourceConn)
c.done()
reterr = newError("failed to peek tls record").Base(err).AtWarning()
return handshake, nil, nil, reterr
}
_, err = io.Copy(mirrorConn, bytes.NewReader(readBuffer[nextRead:nextRead+n]))
if err != nil {
c.done()
newError("failed to copy to server connection").Base(err).AtWarning().WriteToLog()
return handshake, nil, nil, reterr
}
nextRead += n
} else {
// Parse the client hello
if result.RecordType != mirrorcommon.TLSRecord_RecordType_handshake {
c.done()
reterr = newError("unexpected record type").AtWarning()
return handshake, nil, nil, reterr
}
handshake = result
handshakeReminder = readBuffer[nextRead:processed]
rest = readBuffer[processed : nextRead+n]
return handshake, handshakeReminder, rest, nil
}
}
reterr = newError("context is done")
return handshake, nil, nil, reterr
}

View File

@ -0,0 +1,9 @@
package mirrorbase
import "github.com/v2fly/v2ray-core/v5/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View File

@ -0,0 +1,40 @@
package mirrorcommon
import (
"bytes"
"github.com/v2fly/struc"
)
type TLSClientHello struct {
HandshakeType uint8
Length [3]byte
Version uint16
ClientRandom [32]byte
// There are other entries, however we do not need them yet
}
func UnpackTLSClientHello(data []byte) (TLSClientHello, error) {
var clientHello TLSClientHello
err := struc.Unpack(bytes.NewReader(data), &clientHello)
return clientHello, err
}
type TLSServerHello struct {
HandshakeType uint8
Length [3]byte
Version uint16
ServerRandom [32]byte
SessionIDLength uint8 `struc:"sizeof=SessionID"`
SessionID []byte
CipherSuite uint16
// There are other entries, however we do not need them yet
}
func UnpackTLSServerHello(data []byte) (TLSServerHello, error) {
var serverHello TLSServerHello
err := struc.Unpack(bytes.NewReader(data), &serverHello)
return serverHello, err
}

View File

@ -0,0 +1,61 @@
package mirrorcommon
import (
"fmt"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror"
)
// PeekTLSRecord reads a TLS record from peeker.
// It returns the record, the number of bytes read from peeker, and any error encountered.
// It does not consume content from peeker, and the content peeked is borrowed from peeker.
func PeekTLSRecord(peeker tlsmirror.Peeker, rejectionProfile tlsmirror.PartialTLSRecordRejectProfile) (result tlsmirror.TLSRecord, tryAgainLength, processed int, err error) {
var record tlsmirror.TLSRecord
header, err := peeker.Peek(5)
if err != nil {
return record, 0, 0, err
}
if len(header) < 5 {
return record, 5, 0, fmt.Errorf("tls: record header too short")
}
record.RecordType = header[0]
record.LegacyProtocolVersion[0] = header[1]
record.LegacyProtocolVersion[1] = header[2]
record.RecordLength = uint16(header[3])<<8 | uint16(header[4])
if record.RecordLength > 16384 {
return record, 0, 0, fmt.Errorf("tls: record length %d is too large", record.RecordLength)
}
if rejectionProfile != nil {
err = rejectionProfile.TestIfReject(&record, 2)
if err != nil {
return record, 0, 0, err
}
}
fragment, err := peeker.Peek(int(5 + record.RecordLength))
if err != nil {
return record, int(5 + record.RecordLength), 0, err
}
if len(fragment) < 5+int(record.RecordLength) {
return record, int(5 + record.RecordLength), 0, fmt.Errorf("tls: record fragment too short")
}
record.Fragment = fragment[5:]
return record, 0, int(5 + record.RecordLength), nil
}
func PackTLSRecord(record tlsmirror.TLSRecord) []byte {
buf := make([]byte, 5+len(record.Fragment))
buf[0] = record.RecordType
buf[1] = record.LegacyProtocolVersion[0]
buf[2] = record.LegacyProtocolVersion[1]
buf[3] = byte(record.RecordLength >> 8)
buf[4] = byte(record.RecordLength)
copy(buf[5:], record.Fragment)
return buf
}
func DuplicateRecord(record tlsmirror.TLSRecord) tlsmirror.TLSRecord {
newRecord := record
newRecord.Fragment = make([]byte, len(record.Fragment))
copy(newRecord.Fragment, record.Fragment)
return newRecord
}

View File

@ -0,0 +1,6 @@
package mirrorcommon
const (
TLSRecord_RecordType_application_data = 23
TLSRecord_RecordType_handshake = 22
)

View File

@ -0,0 +1,73 @@
package mirrorcommon
import (
"bufio"
"errors"
"io"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror"
)
func NewTLSRecordStreamReader(reader *bufio.Reader) *TLSRecordStreamReader {
return &TLSRecordStreamReader{bufferedReader: reader}
}
type TLSRecordStreamReader struct {
bufferedReader *bufio.Reader
consumedSize int64
}
func (t *TLSRecordStreamReader) ReadNextRecord() (*tlsmirror.TLSRecord, error) {
record, tryAgainLength, processedLength, err := PeekTLSRecord(t.bufferedReader, nil)
if err == nil {
_, err := t.bufferedReader.Discard(processedLength)
t.consumedSize += int64(processedLength)
if err != nil {
return nil, err
}
return &record, nil
}
if errors.Is(err, io.EOF) {
return nil, err
} else { // nolint: gocritic
if tryAgainLength == 0 {
return nil, err
}
}
if tryAgainLength > 0 {
_, err := t.bufferedReader.Read(make([]byte, tryAgainLength))
if err != nil {
return nil, err
}
err = t.bufferedReader.UnreadByte()
if err != nil {
return nil, err
}
}
return t.ReadNextRecord()
}
func (t *TLSRecordStreamReader) GetConsumedSize() int64 {
return t.consumedSize
}
func NewTLSRecordStreamWriter(writer *bufio.Writer) *TLSRecordStreamWriter {
return &TLSRecordStreamWriter{bufferedWriter: writer}
}
type TLSRecordStreamWriter struct {
bufferedWriter *bufio.Writer
}
func (t *TLSRecordStreamWriter) WriteRecord(record *tlsmirror.TLSRecord, holdFlush bool) error {
_, err := t.bufferedWriter.Write(PackTLSRecord(*record))
if err != nil {
return err
}
if holdFlush {
return nil
}
return t.bufferedWriter.Flush()
}

View File

@ -0,0 +1,37 @@
package mirrorcrypto
import (
"crypto/cipher"
"github.com/v2fly/v2ray-core/v5/common/crypto"
)
type Decryptor struct {
nonceGenerator crypto.BytesGenerator
aead cipher.AEAD
nextNonce []byte
}
func NewDecryptor(encryptionKey []byte, nonceMask []byte) *Decryptor {
wrappedAead := aeadAESGCMTLS13(encryptionKey, nonceMask)
return &Decryptor{
nonceGenerator: generateInitialAEADNonce(),
aead: wrappedAead,
}
}
func (d *Decryptor) Open(dst, src []byte) ([]byte, error) {
if d.nextNonce == nil {
d.nextNonce = d.nonceGenerator()
}
dst, err := d.aead.Open(dst[:0], d.nextNonce, src, nil)
if err != nil {
return nil, err
}
d.nextNonce = nil
return dst, nil
}
func (d *Decryptor) NonceSize() int {
return d.aead.NonceSize()
}

View File

@ -0,0 +1,34 @@
package mirrorcrypto
import (
"crypto/hkdf"
"crypto/sha256"
)
func DeriveEncryptionKey(primaryKey, clientRandom, serverRandom []byte, tag string) ([]byte, []byte, error) {
if len(primaryKey) != 32 {
return nil, nil, newError("invalid primary key size: ", len(primaryKey))
}
if len(clientRandom) != 32 {
return nil, nil, newError("invalid client random size: ", len(clientRandom))
}
if len(serverRandom) != 32 {
return nil, nil, newError("invalid server random size: ", len(serverRandom))
}
// Concatenate the primary key, client random, and server random
combined := append(primaryKey, clientRandom...) // nolint: gocritic
combined = append(combined, serverRandom...)
encryptionKey, err := hkdf.Expand(sha256.New, combined, "v2ray-sp76YMKM-EkGrFUNL-rTJRJMkU:tlsmirror-encryption", 16)
if err != nil {
return nil, nil, newError("unable to derive encryption key").Base(err)
}
nonceMask, err := hkdf.Expand(sha256.New, combined, "v2ray-sp76YMKM-EkGrFUNL-rTJRJMkU:tlsmirror-noncemask", 12)
if err != nil {
return nil, nil, newError("unable to derive nonce mask").Base(err)
}
return encryptionKey, nonceMask, nil
}

View File

@ -0,0 +1,30 @@
package mirrorcrypto
import (
"crypto/cipher"
"github.com/v2fly/v2ray-core/v5/common/crypto"
)
type Encryptor struct {
nonceGenerator crypto.BytesGenerator
aead cipher.AEAD
}
func NewEncryptor(encryptionKey []byte, nonceMask []byte) *Encryptor {
wrappedAead := aeadAESGCMTLS13(encryptionKey, nonceMask)
return &Encryptor{
nonceGenerator: generateInitialAEADNonce(),
aead: wrappedAead,
}
}
func (e *Encryptor) Seal(dst, src []byte) ([]byte, error) {
nonce := e.nonceGenerator()
dst = e.aead.Seal(dst, nonce, src, nil)
return dst, nil
}
func (e *Encryptor) NonceSize() int {
return e.aead.NonceSize()
}

View File

@ -0,0 +1,9 @@
package mirrorcrypto
import "github.com/v2fly/v2ray-core/v5/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View File

@ -0,0 +1,9 @@
package mirrorcrypto
import "github.com/v2fly/v2ray-core/v5/common/crypto"
//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
func generateInitialAEADNonce() crypto.BytesGenerator {
return crypto.GenerateIncreasingNonce([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
}

View File

@ -0,0 +1,69 @@
package mirrorcrypto
import (
"crypto/cipher"
"golang.org/x/crypto/chacha20poly1305"
)
const (
aeadNonceLength = 12
)
type aead interface {
cipher.AEAD
// explicitNonceLen returns the number of bytes of explicit nonce
// included in each record. This is eight for older AEADs and
// zero for modern ones.
explicitNonceLen() int
}
// xorNonceAEAD wraps an AEAD by XORing in a fixed pattern to the nonce
// before each call.
type xorNonceAEAD struct {
nonceMask [aeadNonceLength]byte
aead cipher.AEAD
}
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
result := f.aead.Seal(out, f.nonceMask[:], plaintext, additionalData)
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
return result
}
func (f *xorNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
result, err := f.aead.Open(out, f.nonceMask[:], ciphertext, additionalData)
for i, b := range nonce {
f.nonceMask[4+i] ^= b
}
return result, err
}
func aeadChaCha20Poly1305(key, nonceMask []byte) aead {
if len(nonceMask) != aeadNonceLength {
panic("tls: internal error: wrong nonce length")
}
aead, err := chacha20poly1305.New(key)
if err != nil {
panic(err)
}
ret := &xorNonceAEAD{aead: aead}
copy(ret.nonceMask[:], nonceMask)
return ret
}

View File

@ -0,0 +1,11 @@
package mirrorcrypto
import (
"crypto/cipher"
_ "unsafe"
)
// This linkname is necessary to avoid duplicating too many internal packages.
//go:linkname aeadAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
func aeadAESGCMTLS13(key, nonceMask []byte) cipher.AEAD

View File

@ -0,0 +1,235 @@
package server
import (
"context"
"github.com/golang/protobuf/proto"
core "github.com/v2fly/v2ray-core/v5"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/environment"
"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/serial"
"github.com/v2fly/v2ray-core/v5/features/outbound"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorbase"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/tlstrafficgen"
)
func newPersistentMirrorTLSDialer(ctx context.Context, config *Config, serverAddress net.Destination, overrideSecuritySetting proto.Message) (*persistentMirrorTLSDialer, error) {
persistentDialer := &persistentMirrorTLSDialer{
ctx: ctx,
serverAddress: serverAddress,
overridingSecuritySettings: overrideSecuritySetting,
}
err := persistentDialer.init(ctx, config)
if err != nil {
return nil, newError("failed to initialize persistent mirror TLS dialer").Base(err)
}
return persistentDialer, nil
}
type persistentMirrorTLSDialer struct {
ctx context.Context
config *Config
requestNewConnection func(ctx context.Context) error
incomingConnections chan net.Conn
listener *OutboundListener
outbound *Outbound
serverAddress net.Destination
overridingSecuritySettings proto.Message
trafficGenerator *tlstrafficgen.TrafficGenerator
obm outbound.Manager
}
func (d *persistentMirrorTLSDialer) init(ctx context.Context, config *Config) error {
if err := core.RequireFeatures(ctx, func(om outbound.Manager) {
d.obm = om
}); err != nil {
return err
}
d.requestNewConnection = func(ctx context.Context) error {
return nil
}
d.ctx = ctx
d.config = config
d.incomingConnections = make(chan net.Conn, 4)
d.listener = NewOutboundListener()
d.outbound = NewOutbound(d.config.CarrierConnectionTag, d.listener)
go func() {
err := d.outbound.Start()
if err != nil {
newError("failed to start outbound listener").Base(err).AtWarning().WriteToLog()
return
}
if err := d.obm.RemoveHandler(context.Background(), d.config.CarrierConnectionTag); err != nil {
newError("failed to remove existing handler").WriteToLog()
}
err = d.obm.AddHandler(context.Background(), &Outbound{
tag: d.config.CarrierConnectionTag,
listener: d.listener,
})
if err != nil {
newError("failed to add outbound handler").Base(err).AtWarning().WriteToLog()
return
}
for {
var ctx context.Context
conn, err := d.listener.Accept()
if err != nil {
break
}
if ctxGetter, ok := conn.(connectionContextGetter); ok {
ctx = ctxGetter.GetConnectionContext()
} else {
ctx = d.ctx
newError("connection does not implement connectionContextGetter, using default context").AtError().WriteToLog()
}
d.handleIncomingCarrierConnection(ctx, conn)
}
}()
if d.config.EmbeddedTrafficGenerator != nil {
if d.overridingSecuritySettings != nil && d.config.EmbeddedTrafficGenerator.SecuritySettings == nil {
d.config.EmbeddedTrafficGenerator.SecuritySettings = serial.ToTypedMessage(d.overridingSecuritySettings)
}
d.trafficGenerator = tlstrafficgen.NewTrafficGenerator(d.ctx, d.config.EmbeddedTrafficGenerator,
d.serverAddress, d.config.CarrierConnectionTag)
d.requestNewConnection = func(ctx context.Context) error {
go func() {
err := d.trafficGenerator.GenerateNextTraffic(d.ctx)
if err != nil {
newError("failed to generate next traffic").Base(err).AtWarning().WriteToLog()
} else {
newError("traffic generation request sent").AtDebug().WriteToLog()
}
}()
return nil
}
}
return nil
}
func (d *persistentMirrorTLSDialer) handleIncomingCarrierConnection(ctx context.Context, conn net.Conn) {
transportEnvironment := envctx.EnvironmentFromContext(d.ctx).(environment.TransportEnvironment)
dialer := transportEnvironment.OutboundDialer()
forwardConn, err := dialer(d.ctx, d.serverAddress, d.config.ForwardTag)
if err != nil {
newError("failed to dial to destination").Base(err).AtWarning().WriteToLog()
return
}
ctx, cancel := context.WithCancel(ctx)
cconnState := &clientConnState{
ctx: ctx,
done: cancel,
localAddr: conn.LocalAddr(),
remoteAddr: conn.RemoteAddr(),
handler: d.handleIncomingReadyConnection,
primaryKey: d.config.PrimaryKey,
readPipe: make(chan []byte, 1),
}
cconnState.mirrorConn = mirrorbase.NewMirroredTLSConn(ctx, conn, forwardConn, cconnState.onC2SMessage, cconnState.onS2CMessage, conn)
}
type connectionContextGetter interface {
GetConnectionContext() context.Context
}
func (d *persistentMirrorTLSDialer) handleIncomingReadyConnection(conn internet.Connection) {
go func() {
var waitedForReady bool
if getter, ok := conn.(connectionContextGetter); ok {
ctx := getter.GetConnectionContext()
if managedConnectionController := ctx.Value(tlsmirror.TrafficGeneratorManagedConnectionContextKey); managedConnectionController != nil {
if controller, ok := managedConnectionController.(tlsmirror.TrafficGeneratorManagedConnection); ok {
select { // nolint: staticcheck
case <-controller.WaitConnectionReady().Done():
waitedForReady = true
// TODO: connection might become invalid and never ready, handle this case
}
}
}
}
if !waitedForReady {
newError("unable to wait for connection ready, please verify your setup").AtWarning().WriteToLog()
}
d.incomingConnections <- conn
}()
}
func (d *persistentMirrorTLSDialer) Dial(ctx context.Context,
dest net.Destination, settings *internet.MemoryStreamConfig,
) (internet.Connection, error) {
var recvConn net.Conn
select {
case conn := <-d.incomingConnections:
recvConn = conn
default:
err := d.requestNewConnection(ctx)
if err != nil {
return nil, newError("failed to request new connection").Base(err)
}
select { // nolint: staticcheck
case conn := <-d.incomingConnections:
recvConn = conn
}
}
if recvConn == nil {
return nil, newError("failed to receive connection")
}
return recvConn, nil
}
func Dial(ctx context.Context, dest net.Destination, settings *internet.MemoryStreamConfig) (internet.Connection, error) {
transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment)
dialer, err := transportEnvironment.TransientStorage().Get(ctx, "persistentDialer")
if err != nil {
var securitySetting proto.Message
if settings.SecurityType != "" && settings.SecurityType != "none" {
securitySetting = settings.SecuritySettings.(proto.Message)
}
config := settings.ProtocolSettings.(*Config)
detachedContext := core.ToBackgroundDetachedContext(ctx)
dialer, err = newPersistentMirrorTLSDialer(detachedContext, config, dest, securitySetting)
if err != nil {
return nil, newError("failed to create persistent mirror TLS dialer").Base(err)
}
err = transportEnvironment.TransientStorage().Put(ctx, "persistentDialer", dialer)
if err != nil {
return nil, newError("failed to put persistent dialer").Base(err)
}
}
conn, err := dialer.(*persistentMirrorTLSDialer).Dial(ctx, dest, settings)
if err != nil {
return nil, newError("failed to dial").Base(err)
}
return conn, nil
}
func init() {
common.Must(internet.RegisterTransportDialer(protocolName, Dial))
}

View File

@ -0,0 +1,164 @@
package server
import (
"bytes"
"context"
gonet "net"
"time"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorcommon"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorcrypto"
)
type clientConnState struct {
ctx context.Context
done context.CancelFunc
handler internet.ConnHandler
mirrorConn tlsmirror.InsertableTLSConn
localAddr net.Addr
remoteAddr net.Addr
activated bool
decryptor *mirrorcrypto.Decryptor
encryptor *mirrorcrypto.Encryptor
primaryKey []byte
readPipe chan []byte
readBuffer *bytes.Buffer
protocolVersion [2]byte
}
func (s *clientConnState) GetConnectionContext() context.Context {
return s.ctx
}
func (s *clientConnState) Read(b []byte) (n int, err error) {
if s.readBuffer != nil {
n, _ = s.readBuffer.Read(b)
if n > 0 {
return n, nil
}
s.readBuffer = nil
}
select {
case <-s.ctx.Done():
return 0, s.ctx.Err()
case data := <-s.readPipe:
s.readBuffer = bytes.NewBuffer(data)
n, err = s.readBuffer.Read(b)
if err != nil {
return 0, err
}
return n, nil
}
}
func (s *clientConnState) Write(b []byte) (n int, err error) {
err = s.WriteMessage(b)
if err != nil {
return 0, err
}
n = len(b)
return n, nil
}
func (s *clientConnState) Close() error {
s.done()
return nil
}
func (s *clientConnState) LocalAddr() gonet.Addr {
return s.remoteAddr
}
func (s *clientConnState) RemoteAddr() gonet.Addr {
return s.remoteAddr
}
func (s *clientConnState) SetDeadline(t time.Time) error {
return nil
}
func (s *clientConnState) SetReadDeadline(t time.Time) error {
return nil
}
func (s *clientConnState) SetWriteDeadline(t time.Time) error {
return nil
}
func (s *clientConnState) onC2SMessage(message *tlsmirror.TLSRecord) (drop bool, ok error) {
if message.RecordType == mirrorcommon.TLSRecord_RecordType_application_data {
if s.decryptor == nil {
clientRandom, serverRandom, err := s.mirrorConn.GetHandshakeRandom()
if err != nil {
newError("failed to get handshake random").Base(err).AtWarning().WriteToLog()
return false, nil
}
{
encryptionKey, nonceMask, err := mirrorcrypto.DeriveEncryptionKey(s.primaryKey, clientRandom, serverRandom, ":s2c")
if err != nil {
newError("failed to derive C2S encryption key").Base(err).AtWarning().WriteToLog()
return false, nil
}
s.decryptor = mirrorcrypto.NewDecryptor(encryptionKey, nonceMask)
}
{
encryptionKey, nonceMask, err := mirrorcrypto.DeriveEncryptionKey(s.primaryKey, clientRandom, serverRandom, ":c2s")
if err != nil {
newError("failed to derive S2C encryption key").Base(err).AtWarning().WriteToLog()
return false, nil
}
s.encryptor = mirrorcrypto.NewEncryptor(encryptionKey, nonceMask)
}
s.protocolVersion = message.LegacyProtocolVersion
if !s.activated {
s.handler(s)
s.activated = true
}
}
}
return false, ok
}
func (s *clientConnState) onS2CMessage(message *tlsmirror.TLSRecord) (drop bool, ok error) {
if message.RecordType == mirrorcommon.TLSRecord_RecordType_application_data {
if s.encryptor == nil {
return false, nil
}
buffer := make([]byte, 0, len(message.Fragment)-s.encryptor.NonceSize())
buffer, err := s.decryptor.Open(buffer, message.Fragment)
if err != nil {
return false, nil
}
s.readPipe <- buffer
return true, nil
}
return false, ok
}
func (s *clientConnState) WriteMessage(message []byte) error {
buffer := make([]byte, 0, len(message)+s.encryptor.NonceSize())
buffer, err := s.encryptor.Seal(buffer, message)
if err != nil {
return newError("failed to encrypt message").Base(err)
}
record := tlsmirror.TLSRecord{
RecordType: mirrorcommon.TLSRecord_RecordType_application_data,
LegacyProtocolVersion: s.protocolVersion,
RecordLength: uint16(len(buffer)),
Fragment: buffer,
}
return s.mirrorConn.InsertC2SMessage(&record)
}

View File

@ -0,0 +1,169 @@
package server
import (
_ "github.com/v2fly/v2ray-core/v5/common/protoext"
tlstrafficgen "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/tlstrafficgen"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
ForwardAddress string `protobuf:"bytes,1,opt,name=forward_address,json=forwardAddress,proto3" json:"forward_address,omitempty"`
ForwardPort uint32 `protobuf:"varint,2,opt,name=forward_port,json=forwardPort,proto3" json:"forward_port,omitempty"`
ForwardTag string `protobuf:"bytes,3,opt,name=forward_tag,json=forwardTag,proto3" json:"forward_tag,omitempty"`
CarrierConnectionTag string `protobuf:"bytes,4,opt,name=carrier_connection_tag,json=carrierConnectionTag,proto3" json:"carrier_connection_tag,omitempty"`
EmbeddedTrafficGenerator *tlstrafficgen.Config `protobuf:"bytes,5,opt,name=embedded_traffic_generator,json=embeddedTrafficGenerator,proto3" json:"embedded_traffic_generator,omitempty"`
PrimaryKey []byte `protobuf:"bytes,6,opt,name=primary_key,json=primaryKey,proto3" json:"primary_key,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_tlsmirror_server_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_tlsmirror_server_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_tlsmirror_server_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetForwardAddress() string {
if x != nil {
return x.ForwardAddress
}
return ""
}
func (x *Config) GetForwardPort() uint32 {
if x != nil {
return x.ForwardPort
}
return 0
}
func (x *Config) GetForwardTag() string {
if x != nil {
return x.ForwardTag
}
return ""
}
func (x *Config) GetCarrierConnectionTag() string {
if x != nil {
return x.CarrierConnectionTag
}
return ""
}
func (x *Config) GetEmbeddedTrafficGenerator() *tlstrafficgen.Config {
if x != nil {
return x.EmbeddedTrafficGenerator
}
return nil
}
func (x *Config) GetPrimaryKey() []byte {
if x != nil {
return x.PrimaryKey
}
return nil
}
var File_transport_internet_tlsmirror_server_config_proto protoreflect.FileDescriptor
const file_transport_internet_tlsmirror_server_config_proto_rawDesc = "" +
"\n" +
"0transport/internet/tlsmirror/server/config.proto\x12.v2ray.core.transport.internet.tlsmirror.server\x1a common/protoext/extensions.proto\x1a7transport/internet/tlsmirror/tlstrafficgen/config.proto\"\xf6\x02\n" +
"\x06Config\x12'\n" +
"\x0fforward_address\x18\x01 \x01(\tR\x0eforwardAddress\x12!\n" +
"\fforward_port\x18\x02 \x01(\rR\vforwardPort\x12\x1f\n" +
"\vforward_tag\x18\x03 \x01(\tR\n" +
"forwardTag\x124\n" +
"\x16carrier_connection_tag\x18\x04 \x01(\tR\x14carrierConnectionTag\x12{\n" +
"\x1aembedded_traffic_generator\x18\x05 \x01(\v2=.v2ray.core.transport.internet.tlsmirror.tlstrafficgen.ConfigR\x18embeddedTrafficGenerator\x12\x1f\n" +
"\vprimary_key\x18\x06 \x01(\fR\n" +
"primaryKey:+\x82\xb5\x18'\n" +
"\ttransport\x12\ttlsmirror\x8a\xff)\ttlsmirror\x90\xff)\x01B\xab\x01\n" +
"2com.v2ray.core.transport.internet.tlsmirror.serverP\x01ZBgithub.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/server\xaa\x02.V2Ray.Core.Transport.Internet.Tlsmirror.Serverb\x06proto3"
var (
file_transport_internet_tlsmirror_server_config_proto_rawDescOnce sync.Once
file_transport_internet_tlsmirror_server_config_proto_rawDescData []byte
)
func file_transport_internet_tlsmirror_server_config_proto_rawDescGZIP() []byte {
file_transport_internet_tlsmirror_server_config_proto_rawDescOnce.Do(func() {
file_transport_internet_tlsmirror_server_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_tlsmirror_server_config_proto_rawDesc), len(file_transport_internet_tlsmirror_server_config_proto_rawDesc)))
})
return file_transport_internet_tlsmirror_server_config_proto_rawDescData
}
var file_transport_internet_tlsmirror_server_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_transport_internet_tlsmirror_server_config_proto_goTypes = []any{
(*Config)(nil), // 0: v2ray.core.transport.internet.tlsmirror.server.Config
(*tlstrafficgen.Config)(nil), // 1: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Config
}
var file_transport_internet_tlsmirror_server_config_proto_depIdxs = []int32{
1, // 0: v2ray.core.transport.internet.tlsmirror.server.Config.embedded_traffic_generator:type_name -> v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Config
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_transport_internet_tlsmirror_server_config_proto_init() }
func file_transport_internet_tlsmirror_server_config_proto_init() {
if File_transport_internet_tlsmirror_server_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_tlsmirror_server_config_proto_rawDesc), len(file_transport_internet_tlsmirror_server_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_tlsmirror_server_config_proto_goTypes,
DependencyIndexes: file_transport_internet_tlsmirror_server_config_proto_depIdxs,
MessageInfos: file_transport_internet_tlsmirror_server_config_proto_msgTypes,
}.Build()
File_transport_internet_tlsmirror_server_config_proto = out.File
file_transport_internet_tlsmirror_server_config_proto_goTypes = nil
file_transport_internet_tlsmirror_server_config_proto_depIdxs = nil
}

View File

@ -0,0 +1,28 @@
syntax = "proto3";
package v2ray.core.transport.internet.tlsmirror.server;
option csharp_namespace = "V2Ray.Core.Transport.Internet.Tlsmirror.Server";
option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/server";
option java_package = "com.v2ray.core.transport.internet.tlsmirror.server";
option java_multiple_files = true;
import "common/protoext/extensions.proto";
import "transport/internet/tlsmirror/tlstrafficgen/config.proto";
message Config {
option (v2ray.core.common.protoext.message_opt).type = "transport";
option (v2ray.core.common.protoext.message_opt).short_name = "tlsmirror";
option (v2ray.core.common.protoext.message_opt).allow_restricted_mode_load = true;
option (v2ray.core.common.protoext.message_opt).transport_original_name = "tlsmirror";
string forward_address = 1;
uint32 forward_port = 2;
string forward_tag = 3;
string carrier_connection_tag = 4;
v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Config embedded_traffic_generator = 5;
bytes primary_key = 6;
}

View File

@ -0,0 +1,150 @@
package server
import (
"bytes"
"context"
"net"
"time"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorcommon"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorcrypto"
)
type connState struct {
ctx context.Context
done context.CancelFunc
handler internet.ConnHandler
mirrorConn tlsmirror.InsertableTLSConn
localAddr net.Addr
remoteAddr net.Addr
activated bool
decryptor *mirrorcrypto.Decryptor
encryptor *mirrorcrypto.Encryptor
primaryKey []byte
readPipe chan []byte
readBuffer *bytes.Buffer
protocolVersion [2]byte
}
func (s *connState) Read(b []byte) (n int, err error) {
if s.readBuffer != nil {
n, _ = s.readBuffer.Read(b)
if n > 0 {
return n, nil
}
s.readBuffer = nil
}
select {
case <-s.ctx.Done():
return 0, s.ctx.Err()
case data := <-s.readPipe:
s.readBuffer = bytes.NewBuffer(data)
n, err = s.readBuffer.Read(b)
if err != nil {
return 0, err
}
return n, nil
}
}
func (s *connState) Write(b []byte) (n int, err error) {
err = s.WriteMessage(b)
if err != nil {
return 0, err
}
n = len(b)
return n, nil
}
func (s *connState) LocalAddr() net.Addr {
return s.localAddr
}
func (s *connState) RemoteAddr() net.Addr {
return s.remoteAddr
}
func (s *connState) SetDeadline(t time.Time) error {
return nil
}
func (s *connState) SetReadDeadline(t time.Time) error {
return nil
}
func (s *connState) SetWriteDeadline(t time.Time) error {
return nil
}
func (s *connState) Close() error {
s.done()
return nil
}
func (s *connState) onC2SMessage(message *tlsmirror.TLSRecord) (drop bool, ok error) {
if message.RecordType == mirrorcommon.TLSRecord_RecordType_application_data {
if s.decryptor == nil {
clientRandom, serverRandom, err := s.mirrorConn.GetHandshakeRandom()
if err != nil {
newError("failed to get handshake random").Base(err).AtWarning().WriteToLog()
return false, nil
}
{
encryptionKey, nonceMask, err := mirrorcrypto.DeriveEncryptionKey(s.primaryKey, clientRandom, serverRandom, ":c2s")
if err != nil {
newError("failed to derive C2S encryption key").Base(err).AtWarning().WriteToLog()
return false, nil
}
s.decryptor = mirrorcrypto.NewDecryptor(encryptionKey, nonceMask)
}
{
encryptionKey, nonceMask, err := mirrorcrypto.DeriveEncryptionKey(s.primaryKey, clientRandom, serverRandom, ":s2c")
if err != nil {
newError("failed to derive S2C encryption key").Base(err).AtWarning().WriteToLog()
return false, nil
}
s.encryptor = mirrorcrypto.NewEncryptor(encryptionKey, nonceMask)
}
s.protocolVersion = message.LegacyProtocolVersion
}
buffer := make([]byte, 0, len(message.Fragment)-s.decryptor.NonceSize())
buffer, err := s.decryptor.Open(buffer, message.Fragment)
if err != nil {
return false, nil
}
if !s.activated {
s.handler(s)
s.activated = true
}
s.readPipe <- buffer
return true, nil
}
return false, ok
}
func (s *connState) WriteMessage(message []byte) error {
buffer := make([]byte, 0, len(message)+s.decryptor.NonceSize())
buffer, err := s.encryptor.Seal(buffer, message)
if err != nil {
return newError("failed to encrypt message").Base(err)
}
record := tlsmirror.TLSRecord{
RecordType: mirrorcommon.TLSRecord_RecordType_application_data,
LegacyProtocolVersion: s.protocolVersion,
RecordLength: uint16(len(buffer)),
Fragment: buffer,
}
return s.mirrorConn.InsertS2CMessage(&record)
}

View File

@ -0,0 +1,9 @@
package server
import "github.com/v2fly/v2ray-core/v5/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View File

@ -0,0 +1,38 @@
package server
import (
"context"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/transportcommon"
)
func ListenTLSMirror(ctx context.Context, address net.Address, port net.Port,
streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler,
) (internet.Listener, error) {
tlsMirrorSettings := streamSettings.ProtocolSettings.(*Config)
listener, err := transportcommon.ListenWithSecuritySettings(ctx, address, port, streamSettings)
if err != nil {
return nil, newError("failed to listen TLS mirror").Base(err)
}
tlsMirrorServer := &Server{ctx: ctx, listener: listener, config: tlsMirrorSettings, handler: handler}
go tlsMirrorServer.accepts()
return tlsMirrorServer, nil
}
const protocolName = "tlsmirror"
func init() {
common.Must(internet.RegisterTransportListener(protocolName, ListenTLSMirror))
}
func init() {
common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
return new(Config)
}))
}

View File

@ -0,0 +1,131 @@
package server
import (
"context"
"sync"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/signal/done"
"github.com/v2fly/v2ray-core/v5/transport"
)
func NewOutboundListener() *OutboundListener {
return &OutboundListener{
buffer: make(chan *connWithContext, 4),
done: done.New(),
}
}
type connWithContext struct {
net.Conn
ctx context.Context
}
func (c *connWithContext) GetConnectionContext() context.Context {
return c.ctx
}
// OutboundListener is a net.Listener for listening gRPC connections.
type OutboundListener struct {
buffer chan *connWithContext
done *done.Instance
}
func (l *OutboundListener) addWithContext(ctx context.Context, conn net.Conn) {
select {
case l.buffer <- &connWithContext{Conn: conn, ctx: ctx}:
case <-l.done.Wait():
conn.Close()
default:
conn.Close()
}
}
// Accept implements net.Listener.
func (l *OutboundListener) Accept() (net.Conn, error) {
select {
case <-l.done.Wait():
return nil, newError("listen closed")
case c := <-l.buffer:
return c, nil
}
}
// Close implement net.Listener.
func (l *OutboundListener) Close() error {
common.Must(l.done.Close())
L:
for {
select {
case c := <-l.buffer:
c.Close()
default:
break L
}
}
return nil
}
// Addr implements net.Listener.
func (l *OutboundListener) Addr() net.Addr {
return &net.TCPAddr{
IP: net.IP{0, 0, 0, 0},
Port: 0,
}
}
func NewOutbound(tag string, listener *OutboundListener) *Outbound {
return &Outbound{
tag: tag,
listener: listener,
}
}
// Outbound is a outbound.Handler that handles gRPC connections.
type Outbound struct {
tag string
listener *OutboundListener
access sync.RWMutex
closed bool
}
// Dispatch implements outbound.Handler.
func (co *Outbound) Dispatch(ctx context.Context, link *transport.Link) {
co.access.RLock()
if co.closed {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
co.access.RUnlock()
return
}
closeSignal := done.New()
c := net.NewConnection(net.ConnectionInputMulti(link.Writer), net.ConnectionOutputMulti(link.Reader), net.ConnectionOnClose(closeSignal))
co.listener.addWithContext(ctx, c)
co.access.RUnlock()
<-closeSignal.Wait()
}
// Tag implements outbound.Handler.
func (co *Outbound) Tag() string {
return co.tag
}
// Start implements common.Runnable.
func (co *Outbound) Start() error {
co.access.Lock()
co.closed = false
co.access.Unlock()
return nil
}
// Close implements common.Closable.
func (co *Outbound) Close() error {
co.access.Lock()
defer co.access.Unlock()
co.closed = true
return co.listener.Close()
}

View File

@ -0,0 +1,93 @@
package server
import (
"strings"
"time"
"golang.org/x/net/context"
"github.com/v2fly/v2ray-core/v5/common/environment"
"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorbase"
)
//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
type Server struct {
config *Config
listener net.Listener
handler internet.ConnHandler
ctx context.Context
}
func (s *Server) process(conn net.Conn) {
transportEnvironment := envctx.EnvironmentFromContext(s.ctx).(environment.TransportEnvironment)
dialer := transportEnvironment.OutboundDialer()
port, err := net.PortFromInt(s.config.ForwardPort)
if err != nil {
newError("failed to parse port").Base(err).AtWarning().WriteToLog()
return
}
address := net.ParseAddress(s.config.ForwardAddress)
dest := net.TCPDestination(address, port)
forwardConn, err := dialer(s.ctx, dest, s.config.ForwardTag)
if err != nil {
newError("failed to dial to destination").Base(err).AtWarning().WriteToLog()
return
}
s.accept(conn, forwardConn)
}
func (s *Server) accepts() {
for {
conn, err := s.listener.Accept()
if err != nil {
errStr := err.Error()
if strings.Contains(errStr, "closed") {
break
}
newError("failed to accepted raw connections").Base(err).AtWarning().WriteToLog()
if strings.Contains(errStr, "too many") {
time.Sleep(time.Millisecond * 500)
}
continue
}
go s.process(conn)
}
}
func (s *Server) Close() error {
return s.listener.Close()
}
func (s *Server) Addr() net.Addr {
return s.listener.Addr()
}
func (s *Server) accept(clientConn net.Conn, serverConn net.Conn) {
ctx, cancel := context.WithCancel(s.ctx)
conn := &connState{
ctx: ctx,
done: cancel,
localAddr: clientConn.LocalAddr(),
remoteAddr: clientConn.RemoteAddr(),
primaryKey: s.config.PrimaryKey,
handler: s.onIncomingReadyConnection,
readPipe: make(chan []byte, 1),
}
conn.mirrorConn = mirrorbase.NewMirroredTLSConn(ctx, clientConn, serverConn, conn.onC2SMessage, nil, conn)
}
func (s *Server) onIncomingReadyConnection(conn internet.Connection) {
go s.handler(conn)
}

View File

@ -0,0 +1,383 @@
package tlstrafficgen
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
anypb "google.golang.org/protobuf/types/known/anypb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Header struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
Values []string `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Header) Reset() {
*x = Header{}
mi := &file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Header) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Header) ProtoMessage() {}
func (x *Header) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Header.ProtoReflect.Descriptor instead.
func (*Header) Descriptor() ([]byte, []int) {
return file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescGZIP(), []int{0}
}
func (x *Header) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Header) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
func (x *Header) GetValues() []string {
if x != nil {
return x.Values
}
return nil
}
type TransferCandidate struct {
state protoimpl.MessageState `protogen:"open.v1"`
Weight int32 `protobuf:"varint,1,opt,name=weight,proto3" json:"weight,omitempty"`
GotoLocation int64 `protobuf:"varint,2,opt,name=goto_location,json=gotoLocation,proto3" json:"goto_location,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TransferCandidate) Reset() {
*x = TransferCandidate{}
mi := &file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TransferCandidate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransferCandidate) ProtoMessage() {}
func (x *TransferCandidate) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransferCandidate.ProtoReflect.Descriptor instead.
func (*TransferCandidate) Descriptor() ([]byte, []int) {
return file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescGZIP(), []int{1}
}
func (x *TransferCandidate) GetWeight() int32 {
if x != nil {
return x.Weight
}
return 0
}
func (x *TransferCandidate) GetGotoLocation() int64 {
if x != nil {
return x.GotoLocation
}
return 0
}
type Step struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Host string `protobuf:"bytes,8,opt,name=host,proto3" json:"host,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
WaitAtLeast float32 `protobuf:"fixed32,4,opt,name=wait_at_least,json=waitAtLeast,proto3" json:"wait_at_least,omitempty"`
WaitAtMost float32 `protobuf:"fixed32,5,opt,name=wait_at_most,json=waitAtMost,proto3" json:"wait_at_most,omitempty"`
NextStep []*TransferCandidate `protobuf:"bytes,6,rep,name=next_step,json=nextStep,proto3" json:"next_step,omitempty"`
ConnectionReady bool `protobuf:"varint,7,opt,name=connection_ready,json=connectionReady,proto3" json:"connection_ready,omitempty"`
Headers []*Header `protobuf:"bytes,9,rep,name=headers,proto3" json:"headers,omitempty"`
ConnectionRecallExit bool `protobuf:"varint,10,opt,name=connection_recall_exit,json=connectionRecallExit,proto3" json:"connection_recall_exit,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Step) Reset() {
*x = Step{}
mi := &file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Step) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Step) ProtoMessage() {}
func (x *Step) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Step.ProtoReflect.Descriptor instead.
func (*Step) Descriptor() ([]byte, []int) {
return file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescGZIP(), []int{2}
}
func (x *Step) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Step) GetHost() string {
if x != nil {
return x.Host
}
return ""
}
func (x *Step) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *Step) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *Step) GetWaitAtLeast() float32 {
if x != nil {
return x.WaitAtLeast
}
return 0
}
func (x *Step) GetWaitAtMost() float32 {
if x != nil {
return x.WaitAtMost
}
return 0
}
func (x *Step) GetNextStep() []*TransferCandidate {
if x != nil {
return x.NextStep
}
return nil
}
func (x *Step) GetConnectionReady() bool {
if x != nil {
return x.ConnectionReady
}
return false
}
func (x *Step) GetHeaders() []*Header {
if x != nil {
return x.Headers
}
return nil
}
func (x *Step) GetConnectionRecallExit() bool {
if x != nil {
return x.ConnectionRecallExit
}
return false
}
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
Steps []*Step `protobuf:"bytes,1,rep,name=steps,proto3" json:"steps,omitempty"`
SecuritySettings *anypb.Any `protobuf:"bytes,2,opt,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescGZIP(), []int{3}
}
func (x *Config) GetSteps() []*Step {
if x != nil {
return x.Steps
}
return nil
}
func (x *Config) GetSecuritySettings() *anypb.Any {
if x != nil {
return x.SecuritySettings
}
return nil
}
var File_transport_internet_tlsmirror_tlstrafficgen_config_proto protoreflect.FileDescriptor
const file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDesc = "" +
"\n" +
"7transport/internet/tlsmirror/tlstrafficgen/config.proto\x125v2ray.core.transport.internet.tlsmirror.tlstrafficgen\x1a\x19google/protobuf/any.proto\"J\n" +
"\x06Header\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value\x12\x16\n" +
"\x06values\x18\x03 \x03(\tR\x06values\"P\n" +
"\x11TransferCandidate\x12\x16\n" +
"\x06weight\x18\x01 \x01(\x05R\x06weight\x12#\n" +
"\rgoto_location\x18\x02 \x01(\x03R\fgotoLocation\"\xc1\x03\n" +
"\x04Step\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
"\x04host\x18\b \x01(\tR\x04host\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12\x16\n" +
"\x06method\x18\x03 \x01(\tR\x06method\x12\"\n" +
"\rwait_at_least\x18\x04 \x01(\x02R\vwaitAtLeast\x12 \n" +
"\fwait_at_most\x18\x05 \x01(\x02R\n" +
"waitAtMost\x12e\n" +
"\tnext_step\x18\x06 \x03(\v2H.v2ray.core.transport.internet.tlsmirror.tlstrafficgen.TransferCandidateR\bnextStep\x12)\n" +
"\x10connection_ready\x18\a \x01(\bR\x0fconnectionReady\x12W\n" +
"\aheaders\x18\t \x03(\v2=.v2ray.core.transport.internet.tlsmirror.tlstrafficgen.HeaderR\aheaders\x124\n" +
"\x16connection_recall_exit\x18\n" +
" \x01(\bR\x14connectionRecallExit\"\x9e\x01\n" +
"\x06Config\x12Q\n" +
"\x05steps\x18\x01 \x03(\v2;.v2ray.core.transport.internet.tlsmirror.tlstrafficgen.StepR\x05steps\x12A\n" +
"\x11security_settings\x18\x02 \x01(\v2\x14.google.protobuf.AnyR\x10securitySettingsB\xc0\x01\n" +
"9com.v2ray.core.transport.internet.tlsmirror.tlstrafficgenP\x01ZIgithub.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/tlstrafficgen\xaa\x025V2Ray.Core.Transport.Internet.Tlsmirror.Tlstrafficgenb\x06proto3"
var (
file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescOnce sync.Once
file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescData []byte
)
func file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescGZIP() []byte {
file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescOnce.Do(func() {
file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDesc), len(file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDesc)))
})
return file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDescData
}
var file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_transport_internet_tlsmirror_tlstrafficgen_config_proto_goTypes = []any{
(*Header)(nil), // 0: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Header
(*TransferCandidate)(nil), // 1: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.TransferCandidate
(*Step)(nil), // 2: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Step
(*Config)(nil), // 3: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Config
(*anypb.Any)(nil), // 4: google.protobuf.Any
}
var file_transport_internet_tlsmirror_tlstrafficgen_config_proto_depIdxs = []int32{
1, // 0: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Step.next_step:type_name -> v2ray.core.transport.internet.tlsmirror.tlstrafficgen.TransferCandidate
0, // 1: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Step.headers:type_name -> v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Header
2, // 2: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Config.steps:type_name -> v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Step
4, // 3: v2ray.core.transport.internet.tlsmirror.tlstrafficgen.Config.security_settings:type_name -> google.protobuf.Any
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_transport_internet_tlsmirror_tlstrafficgen_config_proto_init() }
func file_transport_internet_tlsmirror_tlstrafficgen_config_proto_init() {
if File_transport_internet_tlsmirror_tlstrafficgen_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDesc), len(file_transport_internet_tlsmirror_tlstrafficgen_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_tlsmirror_tlstrafficgen_config_proto_goTypes,
DependencyIndexes: file_transport_internet_tlsmirror_tlstrafficgen_config_proto_depIdxs,
MessageInfos: file_transport_internet_tlsmirror_tlstrafficgen_config_proto_msgTypes,
}.Build()
File_transport_internet_tlsmirror_tlstrafficgen_config_proto = out.File
file_transport_internet_tlsmirror_tlstrafficgen_config_proto_goTypes = nil
file_transport_internet_tlsmirror_tlstrafficgen_config_proto_depIdxs = nil
}

View File

@ -0,0 +1,38 @@
syntax = "proto3";
package v2ray.core.transport.internet.tlsmirror.tlstrafficgen;
option csharp_namespace = "V2Ray.Core.Transport.Internet.Tlsmirror.Tlstrafficgen";
option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/tlstrafficgen";
option java_package = "com.v2ray.core.transport.internet.tlsmirror.tlstrafficgen";
option java_multiple_files = true;
import "google/protobuf/any.proto";
message Header {
string name = 1;
string value = 2;
repeated string values = 3;
}
message TransferCandidate{
int32 weight = 1;
int64 goto_location = 2;
}
message Step {
string name = 1;
string host = 8;
string path = 2;
string method = 3;
float wait_at_least = 4;
float wait_at_most = 5;
repeated TransferCandidate next_step = 6;
bool connection_ready = 7;
repeated Header headers = 9;
bool connection_recall_exit = 10;
}
message Config {
repeated Step steps = 1;
google.protobuf.Any security_settings = 2;
}

View File

@ -0,0 +1,9 @@
package tlstrafficgen
import "github.com/v2fly/v2ray-core/v5/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View File

@ -0,0 +1,299 @@
package tlstrafficgen
import (
"bufio"
"context"
cryptoRand "crypto/rand"
"io"
"math/big"
"net/http"
"net/url"
"time"
"golang.org/x/net/http2"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/environment"
"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/serial"
"github.com/v2fly/v2ray-core/v5/transport/internet/security"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror"
)
//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
type TrafficGenerator struct {
config *Config
ctx context.Context
destination net.Destination
tag string
}
func NewTrafficGenerator(ctx context.Context, config *Config, destination net.Destination, tag string) *TrafficGenerator {
return &TrafficGenerator{
ctx: ctx,
config: config,
destination: destination,
tag: tag,
}
}
type trafficGeneratorManagedConnectionController struct {
readyCtx context.Context
readyDone context.CancelFunc
recallCtx context.Context
recallDone context.CancelFunc
}
func (t *trafficGeneratorManagedConnectionController) WaitConnectionReady() context.Context {
return t.readyCtx
}
func (t *trafficGeneratorManagedConnectionController) RecallTrafficGenerator() error {
t.recallDone()
return nil
}
// Copied from https://brandur.org/fragments/crypto-rand-float64, Thanks
func randIntN(max int64) int64 {
nBig, err := cryptoRand.Int(cryptoRand.Reader, big.NewInt(max))
if err != nil {
panic(err)
}
return nBig.Int64()
}
func randFloat64() float64 {
return float64(randIntN(1<<53)) / (1 << 53)
}
func (generator *TrafficGenerator) GenerateNextTraffic(ctx context.Context) error {
transportEnvironment := envctx.EnvironmentFromContext(generator.ctx).(environment.TransportEnvironment)
dialer := transportEnvironment.OutboundDialer()
carrierConnectionReadyCtx, carrierConnectionReadyDone := context.WithCancel(generator.ctx)
carrierConnectionRecallCtx, carrierConnectionRecallDone := context.WithCancel(generator.ctx)
trafficController := &trafficGeneratorManagedConnectionController{
readyCtx: carrierConnectionReadyCtx,
readyDone: carrierConnectionReadyDone,
recallCtx: carrierConnectionRecallCtx,
recallDone: carrierConnectionRecallDone,
}
var trafficControllerIfce tlsmirror.TrafficGeneratorManagedConnection = trafficController
managedConnectionContextValue := context.WithValue(generator.ctx,
tlsmirror.TrafficGeneratorManagedConnectionContextKey, trafficControllerIfce) // nolint:staticcheck
conn, err := dialer(managedConnectionContextValue, generator.destination, generator.tag)
if err != nil {
return newError("failed to dial to destination").Base(err).AtWarning()
}
tlsConn, err := generator.tlsHandshake(conn)
if err != nil {
return newError("failed to create TLS connection").Base(err).AtWarning()
}
getAlpn, ok := tlsConn.(security.ConnectionApplicationProtocol)
if !ok {
return newError("TLS connection does not support getting ALPN").AtWarning()
}
alpn, err := getAlpn.GetConnectionApplicationProtocol()
if err != nil {
return newError("failed to get ALPN from TLS connection").Base(err).AtWarning()
}
steps := generator.config.Steps
currentStep := 0
httpRoundTripper, err := newSingleConnectionHTTPTransport(tlsConn, alpn)
if err != nil {
return newError("failed to create HTTP transport").Base(err).AtWarning()
}
for {
if currentStep >= len(steps) {
return tlsConn.Close()
}
step := steps[currentStep]
url := url.URL{
Scheme: "https",
Host: step.Host,
Path: step.Path,
}
httpReq := &http.Request{Host: url.Hostname(), Method: step.Method, URL: &url}
if step.Headers != nil {
header := make(http.Header, len(step.Headers))
for _, v := range step.Headers {
if v.Value != "" {
header.Add(v.Name, v.Value)
}
if v.Values != nil {
for _, value := range v.Values {
header.Add(v.Name, value)
}
}
}
httpReq.Header = header
}
startTime := time.Now()
resp, err := httpRoundTripper.RoundTrip(httpReq)
if err != nil {
return newError("failed to send HTTP request").Base(err).AtWarning()
}
_, err = io.Copy(io.Discard, resp.Body)
if err != nil {
return newError("failed to read HTTP response body").Base(err).AtWarning()
}
err = resp.Body.Close()
if err != nil {
return newError("failed to close HTTP response body").Base(err).AtWarning()
}
endTime := time.Now()
eclipsedTime := endTime.Sub(startTime)
secondToWait := float64(step.WaitAtMost-step.WaitAtLeast)*randFloat64() + float64(step.WaitAtLeast)
if eclipsedTime < time.Duration(secondToWait*float64(time.Second)) {
waitTime := time.Duration(secondToWait*float64(time.Second)) - eclipsedTime
newError("waiting for ", waitTime, " after step ", currentStep).AtDebug().WriteToLog()
waitTimer := time.NewTimer(waitTime)
select {
case <-ctx.Done():
waitTimer.Stop()
return ctx.Err()
case <-waitTimer.C:
}
} else {
newError("step ", currentStep, " took too long: ", eclipsedTime, ", expected: ", secondToWait, " seconds").AtWarning().WriteToLog()
}
if step.ConnectionReady {
carrierConnectionReadyDone()
newError("connection ready for payload traffic").AtInfo().WriteToLog()
}
if step.ConnectionRecallExit {
if carrierConnectionRecallCtx.Err() != nil {
return tlsConn.Close()
}
}
if step.NextStep == nil {
currentStep++
} else {
overallWeight := int32(0)
for _, nextStep := range step.NextStep {
overallWeight += nextStep.Weight
}
maxBound := big.NewInt(int64(overallWeight))
selectionValue, err := cryptoRand.Int(cryptoRand.Reader, maxBound)
if err != nil {
return newError("failed to generate random selection value").Base(err).AtWarning()
}
selectedValue := int32(selectionValue.Int64())
currentValue := int32(0)
matched := false
for _, nextStep := range step.NextStep {
if currentValue >= selectedValue {
currentStep = int(nextStep.GotoLocation)
matched = true
break
}
currentValue += nextStep.Weight
}
if !matched {
newError("invalid steps jump instruction, check configuration for step ", currentStep).AtError().WriteToLog()
currentStep++
}
}
}
}
func (generator *TrafficGenerator) tlsHandshake(conn net.Conn) (security.Conn, error) {
securitySettingInstance, err := serial.GetInstanceOf(generator.config.SecuritySettings)
if err != nil {
return nil, newError("failed to get instance of security settings").Base(err)
}
securityEngine, err := common.CreateObject(generator.ctx, securitySettingInstance)
if err != nil {
return nil, newError("unable to create security engine from security settings").Base(err)
}
securityEngineTyped, ok := securityEngine.(security.Engine)
if !ok {
return nil, newError("type assertion error when create security engine from security settings")
}
return securityEngineTyped.Client(conn)
}
type httpRequestTransport interface {
http.RoundTripper
}
func newHTTPRequestTransportH1(conn net.Conn) httpRequestTransport {
return &httpRequestTransportH1{
conn: conn,
bufReader: bufio.NewReader(conn),
}
}
type httpRequestTransportH1 struct {
conn net.Conn
bufReader *bufio.Reader
}
func (h *httpRequestTransportH1) RoundTrip(req *http.Request) (*http.Response, error) {
req.Proto = "HTTP/1.1"
req.ProtoMajor = 1
req.ProtoMinor = 1
err := req.Write(h.conn)
if err != nil {
return nil, err
}
return http.ReadResponse(h.bufReader, req)
}
func newHTTPRequestTransportH2(conn net.Conn) httpRequestTransport {
transport := &http2.Transport{}
clientConn, err := transport.NewClientConn(conn)
if err != nil {
return nil
}
return &httpRequestTransportH2{
transport: transport,
clientConnection: clientConn,
}
}
type httpRequestTransportH2 struct {
transport *http2.Transport
clientConnection *http2.ClientConn
}
func (h *httpRequestTransportH2) RoundTrip(request *http.Request) (*http.Response, error) {
request.ProtoMajor = 2
request.ProtoMinor = 0
response, err := h.clientConnection.RoundTrip(request)
if err != nil {
return nil, err
}
return response, nil
}
func newSingleConnectionHTTPTransport(conn net.Conn, alpn string) (httpRequestTransport, error) {
switch alpn {
case "h2":
return newHTTPRequestTransportH2(conn), nil
case "http/1.1", "":
return newHTTPRequestTransportH1(conn), nil
default:
return nil, newError("unknown alpn: " + alpn).AtWarning()
}
}