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:
parent
248cba4e43
commit
b1ef737d48
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -256,4 +256,4 @@ func TestSniffFakeQUICPacketWithTooShortData(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
46
transport/internet/tlsmirror/interface.go
Normal file
46
transport/internet/tlsmirror/interface.go
Normal 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
|
||||
}
|
3
transport/internet/tlsmirror/mirrorbase/base.go
Normal file
3
transport/internet/tlsmirror/mirrorbase/base.go
Normal file
@ -0,0 +1,3 @@
|
||||
package mirrorbase
|
||||
|
||||
//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
|
342
transport/internet/tlsmirror/mirrorbase/conn.go
Normal file
342
transport/internet/tlsmirror/mirrorbase/conn.go
Normal 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
|
||||
}
|
@ -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{})
|
||||
}
|
40
transport/internet/tlsmirror/mirrorcommon/handshake.go
Normal file
40
transport/internet/tlsmirror/mirrorcommon/handshake.go
Normal 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
|
||||
}
|
61
transport/internet/tlsmirror/mirrorcommon/record.go
Normal file
61
transport/internet/tlsmirror/mirrorcommon/record.go
Normal 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
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package mirrorcommon
|
||||
|
||||
const (
|
||||
TLSRecord_RecordType_application_data = 23
|
||||
TLSRecord_RecordType_handshake = 22
|
||||
)
|
73
transport/internet/tlsmirror/mirrorcommon/recordstream.go
Normal file
73
transport/internet/tlsmirror/mirrorcommon/recordstream.go
Normal 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()
|
||||
}
|
37
transport/internet/tlsmirror/mirrorcrypto/decryptor.go
Normal file
37
transport/internet/tlsmirror/mirrorcrypto/decryptor.go
Normal 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()
|
||||
}
|
34
transport/internet/tlsmirror/mirrorcrypto/derive_key.go
Normal file
34
transport/internet/tlsmirror/mirrorcrypto/derive_key.go
Normal 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
|
||||
}
|
30
transport/internet/tlsmirror/mirrorcrypto/encrypter.go
Normal file
30
transport/internet/tlsmirror/mirrorcrypto/encrypter.go
Normal 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()
|
||||
}
|
@ -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{})
|
||||
}
|
@ -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})
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
235
transport/internet/tlsmirror/server/client.go
Normal file
235
transport/internet/tlsmirror/server/client.go
Normal 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))
|
||||
}
|
164
transport/internet/tlsmirror/server/client_conn.go
Normal file
164
transport/internet/tlsmirror/server/client_conn.go
Normal 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)
|
||||
}
|
169
transport/internet/tlsmirror/server/config.pb.go
Normal file
169
transport/internet/tlsmirror/server/config.pb.go
Normal 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
|
||||
}
|
28
transport/internet/tlsmirror/server/config.proto
Normal file
28
transport/internet/tlsmirror/server/config.proto
Normal 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;
|
||||
}
|
150
transport/internet/tlsmirror/server/conn.go
Normal file
150
transport/internet/tlsmirror/server/conn.go
Normal 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)
|
||||
}
|
9
transport/internet/tlsmirror/server/errors.generated.go
Normal file
9
transport/internet/tlsmirror/server/errors.generated.go
Normal 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{})
|
||||
}
|
38
transport/internet/tlsmirror/server/hub.go
Normal file
38
transport/internet/tlsmirror/server/hub.go
Normal 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)
|
||||
}))
|
||||
}
|
131
transport/internet/tlsmirror/server/outbound.go
Normal file
131
transport/internet/tlsmirror/server/outbound.go
Normal 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()
|
||||
}
|
93
transport/internet/tlsmirror/server/server.go
Normal file
93
transport/internet/tlsmirror/server/server.go
Normal 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)
|
||||
}
|
383
transport/internet/tlsmirror/tlstrafficgen/config.pb.go
Normal file
383
transport/internet/tlsmirror/tlstrafficgen/config.pb.go
Normal 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
|
||||
}
|
38
transport/internet/tlsmirror/tlstrafficgen/config.proto
Normal file
38
transport/internet/tlsmirror/tlstrafficgen/config.proto
Normal 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;
|
||||
}
|
@ -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{})
|
||||
}
|
299
transport/internet/tlsmirror/tlstrafficgen/trafficgen.go
Normal file
299
transport/internet/tlsmirror/tlstrafficgen/trafficgen.go
Normal 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()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user