mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
header-custom finalmask: Add programmable handshake templates and runtime core (#5920)
https://github.com/XTLS/Xray-core/pull/5920#issuecomment-4252579201 https://github.com/XTLS/Xray-core/pull/5920#issuecomment-4231698135 https://t.me/projectXtls/1829 https://t.me/projectXtls/1640
This commit is contained in:
@@ -113,6 +113,10 @@ type headerManagerConn struct {
|
||||
writeBuf [UDPSize]byte
|
||||
}
|
||||
|
||||
type headerReadAddrAware interface {
|
||||
SetReadAddr(net.Addr)
|
||||
}
|
||||
|
||||
func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
buf := p
|
||||
if len(buf) < UDPSize {
|
||||
@@ -140,6 +144,9 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
|
||||
}
|
||||
|
||||
for i := range c.conns {
|
||||
if aware, ok := c.conns[i].(headerReadAddrAware); ok {
|
||||
aware.SetReadAddr(addr)
|
||||
}
|
||||
n, _, err = c.conns[i].ReadFrom(newBuf)
|
||||
if n == 0 || err != nil {
|
||||
errors.LogDebug(context.Background(), addr, " mask read err ", err)
|
||||
@@ -175,7 +182,7 @@ func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error)
|
||||
n = copy(c.writeBuf[sum:], p)
|
||||
|
||||
for i := len(c.conns) - 1; i >= 0; i-- {
|
||||
n, err = c.conns[i].WriteTo(c.writeBuf[sum-c.sizes[i]:n+sum], nil)
|
||||
n, err = c.conns[i].WriteTo(c.writeBuf[sum-c.sizes[i]:n+sum], addr)
|
||||
if n == 0 || err != nil {
|
||||
errors.LogDebug(context.Background(), addr, " mask write err ", err)
|
||||
return 0, nil
|
||||
|
||||
@@ -21,6 +21,188 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Expr struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Op string `protobuf:"bytes,1,opt,name=op,proto3" json:"op,omitempty"`
|
||||
Args []*ExprArg `protobuf:"bytes,2,rep,name=args,proto3" json:"args,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Expr) Reset() {
|
||||
*x = Expr{}
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Expr) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Expr) ProtoMessage() {}
|
||||
|
||||
func (x *Expr) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_header_custom_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 Expr.ProtoReflect.Descriptor instead.
|
||||
func (*Expr) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Expr) GetOp() string {
|
||||
if x != nil {
|
||||
return x.Op
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Expr) GetArgs() []*ExprArg {
|
||||
if x != nil {
|
||||
return x.Args
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ExprArg struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Value:
|
||||
//
|
||||
// *ExprArg_Bytes
|
||||
// *ExprArg_U64
|
||||
// *ExprArg_Var
|
||||
// *ExprArg_Metadata
|
||||
// *ExprArg_Expr
|
||||
Value isExprArg_Value `protobuf_oneof:"value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ExprArg) Reset() {
|
||||
*x = ExprArg{}
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ExprArg) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ExprArg) ProtoMessage() {}
|
||||
|
||||
func (x *ExprArg) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_header_custom_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 ExprArg.ProtoReflect.Descriptor instead.
|
||||
func (*ExprArg) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ExprArg) GetValue() isExprArg_Value {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExprArg) GetBytes() []byte {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*ExprArg_Bytes); ok {
|
||||
return x.Bytes
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExprArg) GetU64() uint64 {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*ExprArg_U64); ok {
|
||||
return x.U64
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ExprArg) GetVar() string {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*ExprArg_Var); ok {
|
||||
return x.Var
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExprArg) GetMetadata() string {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*ExprArg_Metadata); ok {
|
||||
return x.Metadata
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExprArg) GetExpr() *Expr {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*ExprArg_Expr); ok {
|
||||
return x.Expr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isExprArg_Value interface {
|
||||
isExprArg_Value()
|
||||
}
|
||||
|
||||
type ExprArg_Bytes struct {
|
||||
Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExprArg_U64 struct {
|
||||
U64 uint64 `protobuf:"varint,2,opt,name=u64,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExprArg_Var struct {
|
||||
Var string `protobuf:"bytes,3,opt,name=var,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExprArg_Metadata struct {
|
||||
Metadata string `protobuf:"bytes,4,opt,name=metadata,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExprArg_Expr struct {
|
||||
Expr *Expr `protobuf:"bytes,5,opt,name=expr,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*ExprArg_Bytes) isExprArg_Value() {}
|
||||
|
||||
func (*ExprArg_U64) isExprArg_Value() {}
|
||||
|
||||
func (*ExprArg_Var) isExprArg_Value() {}
|
||||
|
||||
func (*ExprArg_Metadata) isExprArg_Value() {}
|
||||
|
||||
func (*ExprArg_Expr) isExprArg_Value() {}
|
||||
|
||||
type TCPItem struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
DelayMin int64 `protobuf:"varint,1,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
|
||||
@@ -29,13 +211,16 @@ type TCPItem struct {
|
||||
RandMin int32 `protobuf:"varint,4,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
|
||||
RandMax int32 `protobuf:"varint,5,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
|
||||
Packet []byte `protobuf:"bytes,6,opt,name=packet,proto3" json:"packet,omitempty"`
|
||||
Save string `protobuf:"bytes,7,opt,name=save,proto3" json:"save,omitempty"`
|
||||
Var string `protobuf:"bytes,8,opt,name=var,proto3" json:"var,omitempty"`
|
||||
Expr *Expr `protobuf:"bytes,9,opt,name=expr,proto3" json:"expr,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *TCPItem) Reset() {
|
||||
*x = TCPItem{}
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -47,7 +232,7 @@ func (x *TCPItem) String() string {
|
||||
func (*TCPItem) ProtoMessage() {}
|
||||
|
||||
func (x *TCPItem) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -60,7 +245,7 @@ func (x *TCPItem) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use TCPItem.ProtoReflect.Descriptor instead.
|
||||
func (*TCPItem) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{0}
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *TCPItem) GetDelayMin() int64 {
|
||||
@@ -105,6 +290,27 @@ func (x *TCPItem) GetPacket() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *TCPItem) GetSave() string {
|
||||
if x != nil {
|
||||
return x.Save
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *TCPItem) GetVar() string {
|
||||
if x != nil {
|
||||
return x.Var
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *TCPItem) GetExpr() *Expr {
|
||||
if x != nil {
|
||||
return x.Expr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TCPSequence struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Sequence []*TCPItem `protobuf:"bytes,1,rep,name=sequence,proto3" json:"sequence,omitempty"`
|
||||
@@ -114,7 +320,7 @@ type TCPSequence struct {
|
||||
|
||||
func (x *TCPSequence) Reset() {
|
||||
*x = TCPSequence{}
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -126,7 +332,7 @@ func (x *TCPSequence) String() string {
|
||||
func (*TCPSequence) ProtoMessage() {}
|
||||
|
||||
func (x *TCPSequence) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -139,7 +345,7 @@ func (x *TCPSequence) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use TCPSequence.ProtoReflect.Descriptor instead.
|
||||
func (*TCPSequence) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{1}
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *TCPSequence) GetSequence() []*TCPItem {
|
||||
@@ -160,7 +366,7 @@ type TCPConfig struct {
|
||||
|
||||
func (x *TCPConfig) Reset() {
|
||||
*x = TCPConfig{}
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -172,7 +378,7 @@ func (x *TCPConfig) String() string {
|
||||
func (*TCPConfig) ProtoMessage() {}
|
||||
|
||||
func (x *TCPConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -185,7 +391,7 @@ func (x *TCPConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use TCPConfig.ProtoReflect.Descriptor instead.
|
||||
func (*TCPConfig) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{2}
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *TCPConfig) GetClients() []*TCPSequence {
|
||||
@@ -215,13 +421,16 @@ type UDPItem struct {
|
||||
RandMin int32 `protobuf:"varint,2,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
|
||||
RandMax int32 `protobuf:"varint,3,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
|
||||
Packet []byte `protobuf:"bytes,4,opt,name=packet,proto3" json:"packet,omitempty"`
|
||||
Save string `protobuf:"bytes,5,opt,name=save,proto3" json:"save,omitempty"`
|
||||
Var string `protobuf:"bytes,6,opt,name=var,proto3" json:"var,omitempty"`
|
||||
Expr *Expr `protobuf:"bytes,7,opt,name=expr,proto3" json:"expr,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UDPItem) Reset() {
|
||||
*x = UDPItem{}
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -233,7 +442,7 @@ func (x *UDPItem) String() string {
|
||||
func (*UDPItem) ProtoMessage() {}
|
||||
|
||||
func (x *UDPItem) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -246,7 +455,7 @@ func (x *UDPItem) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use UDPItem.ProtoReflect.Descriptor instead.
|
||||
func (*UDPItem) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{3}
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *UDPItem) GetRand() int32 {
|
||||
@@ -277,6 +486,27 @@ func (x *UDPItem) GetPacket() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *UDPItem) GetSave() string {
|
||||
if x != nil {
|
||||
return x.Save
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UDPItem) GetVar() string {
|
||||
if x != nil {
|
||||
return x.Var
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UDPItem) GetExpr() *Expr {
|
||||
if x != nil {
|
||||
return x.Expr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UDPConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Client []*UDPItem `protobuf:"bytes,1,rep,name=client,proto3" json:"client,omitempty"`
|
||||
@@ -287,7 +517,7 @@ type UDPConfig struct {
|
||||
|
||||
func (x *UDPConfig) Reset() {
|
||||
*x = UDPConfig{}
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -299,7 +529,7 @@ func (x *UDPConfig) String() string {
|
||||
func (*UDPConfig) ProtoMessage() {}
|
||||
|
||||
func (x *UDPConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
|
||||
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -312,7 +542,7 @@ func (x *UDPConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use UDPConfig.ProtoReflect.Descriptor instead.
|
||||
func (*UDPConfig) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{4}
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *UDPConfig) GetClient() []*UDPItem {
|
||||
@@ -333,25 +563,41 @@ var File_transport_internet_finalmask_header_custom_config_proto protoreflect.Fi
|
||||
|
||||
const file_transport_internet_finalmask_header_custom_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"7transport/internet/finalmask/header/custom/config.proto\x12/xray.transport.internet.finalmask.header.custom\"\xa5\x01\n" +
|
||||
"7transport/internet/finalmask/header/custom/config.proto\x12/xray.transport.internet.finalmask.header.custom\"d\n" +
|
||||
"\x04Expr\x12\x0e\n" +
|
||||
"\x02op\x18\x01 \x01(\tR\x02op\x12L\n" +
|
||||
"\x04args\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.ExprArgR\x04args\"\xbd\x01\n" +
|
||||
"\aExprArg\x12\x16\n" +
|
||||
"\x05bytes\x18\x01 \x01(\fH\x00R\x05bytes\x12\x12\n" +
|
||||
"\x03u64\x18\x02 \x01(\x04H\x00R\x03u64\x12\x12\n" +
|
||||
"\x03var\x18\x03 \x01(\tH\x00R\x03var\x12\x1c\n" +
|
||||
"\bmetadata\x18\x04 \x01(\tH\x00R\bmetadata\x12K\n" +
|
||||
"\x04expr\x18\x05 \x01(\v25.xray.transport.internet.finalmask.header.custom.ExprH\x00R\x04exprB\a\n" +
|
||||
"\x05value\"\x96\x02\n" +
|
||||
"\aTCPItem\x12\x1b\n" +
|
||||
"\tdelay_min\x18\x01 \x01(\x03R\bdelayMin\x12\x1b\n" +
|
||||
"\tdelay_max\x18\x02 \x01(\x03R\bdelayMax\x12\x12\n" +
|
||||
"\x04rand\x18\x03 \x01(\x05R\x04rand\x12\x19\n" +
|
||||
"\brand_min\x18\x04 \x01(\x05R\arandMin\x12\x19\n" +
|
||||
"\brand_max\x18\x05 \x01(\x05R\arandMax\x12\x16\n" +
|
||||
"\x06packet\x18\x06 \x01(\fR\x06packet\"c\n" +
|
||||
"\x06packet\x18\x06 \x01(\fR\x06packet\x12\x12\n" +
|
||||
"\x04save\x18\a \x01(\tR\x04save\x12\x10\n" +
|
||||
"\x03var\x18\b \x01(\tR\x03var\x12I\n" +
|
||||
"\x04expr\x18\t \x01(\v25.xray.transport.internet.finalmask.header.custom.ExprR\x04expr\"c\n" +
|
||||
"\vTCPSequence\x12T\n" +
|
||||
"\bsequence\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.TCPItemR\bsequence\"\x91\x02\n" +
|
||||
"\tTCPConfig\x12V\n" +
|
||||
"\aclients\x18\x01 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\aclients\x12V\n" +
|
||||
"\aservers\x18\x02 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\aservers\x12T\n" +
|
||||
"\x06errors\x18\x03 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\x06errors\"k\n" +
|
||||
"\x06errors\x18\x03 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\x06errors\"\xdc\x01\n" +
|
||||
"\aUDPItem\x12\x12\n" +
|
||||
"\x04rand\x18\x01 \x01(\x05R\x04rand\x12\x19\n" +
|
||||
"\brand_min\x18\x02 \x01(\x05R\arandMin\x12\x19\n" +
|
||||
"\brand_max\x18\x03 \x01(\x05R\arandMax\x12\x16\n" +
|
||||
"\x06packet\x18\x04 \x01(\fR\x06packet\"\xaf\x01\n" +
|
||||
"\x06packet\x18\x04 \x01(\fR\x06packet\x12\x12\n" +
|
||||
"\x04save\x18\x05 \x01(\tR\x04save\x12\x10\n" +
|
||||
"\x03var\x18\x06 \x01(\tR\x03var\x12I\n" +
|
||||
"\x04expr\x18\a \x01(\v25.xray.transport.internet.finalmask.header.custom.ExprR\x04expr\"\xaf\x01\n" +
|
||||
"\tUDPConfig\x12P\n" +
|
||||
"\x06client\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06client\x12P\n" +
|
||||
"\x06server\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06serverB\xaf\x01\n" +
|
||||
@@ -369,26 +615,32 @@ func file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP()
|
||||
return file_transport_internet_finalmask_header_custom_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_transport_internet_finalmask_header_custom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_transport_internet_finalmask_header_custom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_transport_internet_finalmask_header_custom_config_proto_goTypes = []any{
|
||||
(*TCPItem)(nil), // 0: xray.transport.internet.finalmask.header.custom.TCPItem
|
||||
(*TCPSequence)(nil), // 1: xray.transport.internet.finalmask.header.custom.TCPSequence
|
||||
(*TCPConfig)(nil), // 2: xray.transport.internet.finalmask.header.custom.TCPConfig
|
||||
(*UDPItem)(nil), // 3: xray.transport.internet.finalmask.header.custom.UDPItem
|
||||
(*UDPConfig)(nil), // 4: xray.transport.internet.finalmask.header.custom.UDPConfig
|
||||
(*Expr)(nil), // 0: xray.transport.internet.finalmask.header.custom.Expr
|
||||
(*ExprArg)(nil), // 1: xray.transport.internet.finalmask.header.custom.ExprArg
|
||||
(*TCPItem)(nil), // 2: xray.transport.internet.finalmask.header.custom.TCPItem
|
||||
(*TCPSequence)(nil), // 3: xray.transport.internet.finalmask.header.custom.TCPSequence
|
||||
(*TCPConfig)(nil), // 4: xray.transport.internet.finalmask.header.custom.TCPConfig
|
||||
(*UDPItem)(nil), // 5: xray.transport.internet.finalmask.header.custom.UDPItem
|
||||
(*UDPConfig)(nil), // 6: xray.transport.internet.finalmask.header.custom.UDPConfig
|
||||
}
|
||||
var file_transport_internet_finalmask_header_custom_config_proto_depIdxs = []int32{
|
||||
0, // 0: xray.transport.internet.finalmask.header.custom.TCPSequence.sequence:type_name -> xray.transport.internet.finalmask.header.custom.TCPItem
|
||||
1, // 1: xray.transport.internet.finalmask.header.custom.TCPConfig.clients:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
|
||||
1, // 2: xray.transport.internet.finalmask.header.custom.TCPConfig.servers:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
|
||||
1, // 3: xray.transport.internet.finalmask.header.custom.TCPConfig.errors:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
|
||||
3, // 4: xray.transport.internet.finalmask.header.custom.UDPConfig.client:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
|
||||
3, // 5: xray.transport.internet.finalmask.header.custom.UDPConfig.server:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
|
||||
6, // [6:6] is the sub-list for method output_type
|
||||
6, // [6:6] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
1, // 0: xray.transport.internet.finalmask.header.custom.Expr.args:type_name -> xray.transport.internet.finalmask.header.custom.ExprArg
|
||||
0, // 1: xray.transport.internet.finalmask.header.custom.ExprArg.expr:type_name -> xray.transport.internet.finalmask.header.custom.Expr
|
||||
0, // 2: xray.transport.internet.finalmask.header.custom.TCPItem.expr:type_name -> xray.transport.internet.finalmask.header.custom.Expr
|
||||
2, // 3: xray.transport.internet.finalmask.header.custom.TCPSequence.sequence:type_name -> xray.transport.internet.finalmask.header.custom.TCPItem
|
||||
3, // 4: xray.transport.internet.finalmask.header.custom.TCPConfig.clients:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
|
||||
3, // 5: xray.transport.internet.finalmask.header.custom.TCPConfig.servers:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
|
||||
3, // 6: xray.transport.internet.finalmask.header.custom.TCPConfig.errors:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
|
||||
0, // 7: xray.transport.internet.finalmask.header.custom.UDPItem.expr:type_name -> xray.transport.internet.finalmask.header.custom.Expr
|
||||
5, // 8: xray.transport.internet.finalmask.header.custom.UDPConfig.client:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
|
||||
5, // 9: xray.transport.internet.finalmask.header.custom.UDPConfig.server:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
|
||||
10, // [10:10] is the sub-list for method output_type
|
||||
10, // [10:10] is the sub-list for method input_type
|
||||
10, // [10:10] is the sub-list for extension type_name
|
||||
10, // [10:10] is the sub-list for extension extendee
|
||||
0, // [0:10] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_finalmask_header_custom_config_proto_init() }
|
||||
@@ -396,13 +648,20 @@ func file_transport_internet_finalmask_header_custom_config_proto_init() {
|
||||
if File_transport_internet_finalmask_header_custom_config_proto != nil {
|
||||
return
|
||||
}
|
||||
file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1].OneofWrappers = []any{
|
||||
(*ExprArg_Bytes)(nil),
|
||||
(*ExprArg_U64)(nil),
|
||||
(*ExprArg_Var)(nil),
|
||||
(*ExprArg_Metadata)(nil),
|
||||
(*ExprArg_Expr)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_custom_config_proto_rawDesc), len(file_transport_internet_finalmask_header_custom_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -6,6 +6,21 @@ option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/head
|
||||
option java_package = "com.xray.transport.internet.finalmask.header.custom";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Expr {
|
||||
string op = 1;
|
||||
repeated ExprArg args = 2;
|
||||
}
|
||||
|
||||
message ExprArg {
|
||||
oneof value {
|
||||
bytes bytes = 1;
|
||||
uint64 u64 = 2;
|
||||
string var = 3;
|
||||
string metadata = 4;
|
||||
Expr expr = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message TCPItem {
|
||||
int64 delay_min = 1;
|
||||
int64 delay_max = 2;
|
||||
@@ -13,6 +28,9 @@ message TCPItem {
|
||||
int32 rand_min = 4;
|
||||
int32 rand_max = 5;
|
||||
bytes packet = 6;
|
||||
string save = 7;
|
||||
string var = 8;
|
||||
Expr expr = 9;
|
||||
}
|
||||
|
||||
message TCPSequence {
|
||||
@@ -30,9 +48,12 @@ message UDPItem {
|
||||
int32 rand_min = 2;
|
||||
int32 rand_max = 3;
|
||||
bytes packet = 4;
|
||||
string save = 5;
|
||||
string var = 6;
|
||||
Expr expr = 7;
|
||||
}
|
||||
|
||||
message UDPConfig {
|
||||
repeated UDPItem client = 1;
|
||||
repeated UDPItem server = 2;
|
||||
}
|
||||
}
|
||||
|
||||
406
transport/internet/finalmask/header/custom/evaluator.go
Normal file
406
transport/internet/finalmask/header/custom/evaluator.go
Normal file
@@ -0,0 +1,406 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
type evalValue struct {
|
||||
bytes []byte
|
||||
u64 *uint64
|
||||
}
|
||||
|
||||
type evalContext struct {
|
||||
vars map[string][]byte
|
||||
metadata map[string]evalValue
|
||||
}
|
||||
|
||||
func newEvalContext() *evalContext {
|
||||
return &evalContext{
|
||||
vars: make(map[string][]byte),
|
||||
metadata: make(map[string]evalValue),
|
||||
}
|
||||
}
|
||||
|
||||
func newEvalContextWithAddrs(local, remote net.Addr) *evalContext {
|
||||
ctx := newEvalContext()
|
||||
loadMetadata(ctx.metadata, "local", local)
|
||||
loadMetadata(ctx.metadata, "remote", remote)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func evaluateUDPItems(items []*UDPItem) ([]byte, error) {
|
||||
return evaluateUDPItemsWithContext(items, newEvalContext())
|
||||
}
|
||||
|
||||
func evaluateUDPItemsWithContext(items []*UDPItem, ctx *evalContext) ([]byte, error) {
|
||||
var out []byte
|
||||
for _, item := range items {
|
||||
value, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, value...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func measureUDPItems(items []*UDPItem) (int, error) {
|
||||
return measureUDPItemsWithFallback(items, nil)
|
||||
}
|
||||
|
||||
func measureUDPItemsWithFallback(items []*UDPItem, fallback map[string]int) (int, error) {
|
||||
sizeCtx := make(map[string]int)
|
||||
for key, value := range fallback {
|
||||
sizeCtx[key] = value
|
||||
}
|
||||
total := 0
|
||||
for _, item := range items {
|
||||
itemSize, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeCtx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total += itemSize
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func collectSavedUDPSizes(items []*UDPItem) map[string]int {
|
||||
sizeCtx := make(map[string]int)
|
||||
for _, item := range items {
|
||||
itemSize, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeCtx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if item.Save != "" {
|
||||
sizeCtx[item.Save] = itemSize
|
||||
}
|
||||
}
|
||||
return sizeCtx
|
||||
}
|
||||
|
||||
func measureItem(randLen int32, packet []byte, save, varName string, expr *Expr, sizeCtx map[string]int) (int, error) {
|
||||
var size int
|
||||
switch {
|
||||
case randLen > 0:
|
||||
size = int(randLen)
|
||||
case len(packet) > 0:
|
||||
size = len(packet)
|
||||
case varName != "":
|
||||
length, ok := sizeCtx[varName]
|
||||
if !ok {
|
||||
return 0, errors.New("unknown variable: ", varName)
|
||||
}
|
||||
size = length
|
||||
case expr != nil:
|
||||
exprSize, err := measureExpr(expr, sizeCtx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
size = exprSize
|
||||
default:
|
||||
size = 0
|
||||
}
|
||||
|
||||
if save != "" {
|
||||
sizeCtx[save] = size
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func evaluateTCPSequence(sequence *TCPSequence) ([]byte, error) {
|
||||
ctx := newEvalContext()
|
||||
var out []byte
|
||||
for _, item := range sequence.Sequence {
|
||||
value, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, value...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func evaluateItem(randLen, randMin, randMax int32, packet []byte, save, varName string, expr *Expr, ctx *evalContext) ([]byte, error) {
|
||||
var value []byte
|
||||
switch {
|
||||
case randLen > 0:
|
||||
value = make([]byte, randLen)
|
||||
crypto.RandBytesBetween(value, byte(randMin), byte(randMax))
|
||||
case len(packet) > 0:
|
||||
value = append([]byte(nil), packet...)
|
||||
case varName != "":
|
||||
saved, ok := ctx.vars[varName]
|
||||
if !ok {
|
||||
return nil, errors.New("unknown variable: ", varName)
|
||||
}
|
||||
value = append([]byte(nil), saved...)
|
||||
case expr != nil:
|
||||
evaluated, err := evaluateExpr(expr, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytesValue, err := evaluated.asBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value = bytesValue
|
||||
default:
|
||||
value = nil
|
||||
}
|
||||
|
||||
if save != "" {
|
||||
ctx.vars[save] = append([]byte(nil), value...)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func evaluateExpr(expr *Expr, ctx *evalContext) (evalValue, error) {
|
||||
switch expr.GetOp() {
|
||||
case "concat":
|
||||
var out []byte
|
||||
for _, arg := range expr.GetArgs() {
|
||||
value, err := evaluateExprArg(arg, ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
bytesValue, err := value.asBytes()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
out = append(out, bytesValue...)
|
||||
}
|
||||
return evalValue{bytes: out}, nil
|
||||
case "slice":
|
||||
if len(expr.GetArgs()) != 3 {
|
||||
return evalValue{}, errors.New("slice expects 3 args")
|
||||
}
|
||||
source, err := evaluateExprArg(expr.GetArgs()[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
offset, err := evaluateExprArg(expr.GetArgs()[1], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
length, err := evaluateExprArg(expr.GetArgs()[2], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
sourceBytes, err := source.asBytes()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
offsetU64, err := offset.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
lengthU64, err := length.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
end := offsetU64 + lengthU64
|
||||
if end > uint64(len(sourceBytes)) {
|
||||
return evalValue{}, errors.New("slice out of bounds")
|
||||
}
|
||||
return evalValue{bytes: append([]byte(nil), sourceBytes[offsetU64:end]...)}, nil
|
||||
case "xor16":
|
||||
return evaluateXor(expr.GetArgs(), 0xFFFF, 2, ctx)
|
||||
case "xor32":
|
||||
return evaluateXor(expr.GetArgs(), 0xFFFFFFFF, 4, ctx)
|
||||
case "be16":
|
||||
if len(expr.GetArgs()) != 1 {
|
||||
return evalValue{}, errors.New("be16 expects 1 arg")
|
||||
}
|
||||
value, err := evaluateExprArg(expr.GetArgs()[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
u64Value, err := value.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if u64Value > 0xFFFF {
|
||||
return evalValue{}, errors.New("be16 overflow")
|
||||
}
|
||||
out := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(out, uint16(u64Value))
|
||||
return evalValue{bytes: out}, nil
|
||||
case "be32":
|
||||
if len(expr.GetArgs()) != 1 {
|
||||
return evalValue{}, errors.New("be32 expects 1 arg")
|
||||
}
|
||||
value, err := evaluateExprArg(expr.GetArgs()[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
u64Value, err := value.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if u64Value > 0xFFFFFFFF {
|
||||
return evalValue{}, errors.New("be32 overflow")
|
||||
}
|
||||
out := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(out, uint32(u64Value))
|
||||
return evalValue{bytes: out}, nil
|
||||
default:
|
||||
return evalValue{}, errors.New("unsupported expr op: ", expr.GetOp())
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateXor(args []*ExprArg, mask uint64, width int, ctx *evalContext) (evalValue, error) {
|
||||
if len(args) != 2 {
|
||||
return evalValue{}, errors.New("xor expects 2 args")
|
||||
}
|
||||
left, err := evaluateExprArg(args[0], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
right, err := evaluateExprArg(args[1], ctx)
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
leftU64, err := left.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
rightU64, err := right.asU64()
|
||||
if err != nil {
|
||||
return evalValue{}, err
|
||||
}
|
||||
if width == 2 && (leftU64 > 0xFFFF || rightU64 > 0xFFFF) {
|
||||
return evalValue{}, errors.New("xor16 overflow")
|
||||
}
|
||||
if width == 4 && (leftU64 > 0xFFFFFFFF || rightU64 > 0xFFFFFFFF) {
|
||||
return evalValue{}, errors.New("xor32 overflow")
|
||||
}
|
||||
result := (leftU64 ^ rightU64) & mask
|
||||
return evalValue{u64: &result}, nil
|
||||
}
|
||||
|
||||
func measureExpr(expr *Expr, sizeCtx map[string]int) (int, error) {
|
||||
switch expr.GetOp() {
|
||||
case "concat":
|
||||
total := 0
|
||||
for _, arg := range expr.GetArgs() {
|
||||
size, err := measureExprArg(arg, sizeCtx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total += size
|
||||
}
|
||||
return total, nil
|
||||
case "slice":
|
||||
if len(expr.GetArgs()) != 3 {
|
||||
return 0, errors.New("slice expects 3 args")
|
||||
}
|
||||
lengthArg := expr.GetArgs()[2]
|
||||
if value, ok := lengthArg.GetValue().(*ExprArg_U64); ok {
|
||||
return int(value.U64), nil
|
||||
}
|
||||
return 0, errors.New("slice length must be u64")
|
||||
case "be16":
|
||||
return 2, nil
|
||||
case "be32":
|
||||
return 4, nil
|
||||
default:
|
||||
return 0, errors.New("expr size is not bytes for op: ", expr.GetOp())
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateExprArg(arg *ExprArg, ctx *evalContext) (evalValue, error) {
|
||||
switch value := arg.GetValue().(type) {
|
||||
case *ExprArg_Bytes:
|
||||
return evalValue{bytes: append([]byte(nil), value.Bytes...)}, nil
|
||||
case *ExprArg_U64:
|
||||
return evalValue{u64: &value.U64}, nil
|
||||
case *ExprArg_Var:
|
||||
saved, ok := ctx.vars[value.Var]
|
||||
if !ok {
|
||||
return evalValue{}, errors.New("unknown variable: ", value.Var)
|
||||
}
|
||||
return evalValue{bytes: append([]byte(nil), saved...)}, nil
|
||||
case *ExprArg_Metadata:
|
||||
metadata, ok := ctx.metadata[value.Metadata]
|
||||
if !ok {
|
||||
return evalValue{}, errors.New("unknown metadata: ", value.Metadata)
|
||||
}
|
||||
return metadata, nil
|
||||
case *ExprArg_Expr:
|
||||
return evaluateExpr(value.Expr, ctx)
|
||||
default:
|
||||
return evalValue{}, errors.New("empty expr arg")
|
||||
}
|
||||
}
|
||||
|
||||
func measureExprArg(arg *ExprArg, sizeCtx map[string]int) (int, error) {
|
||||
switch value := arg.GetValue().(type) {
|
||||
case *ExprArg_Bytes:
|
||||
return len(value.Bytes), nil
|
||||
case *ExprArg_U64:
|
||||
return 0, errors.New("u64 arg has no byte width")
|
||||
case *ExprArg_Var:
|
||||
length, ok := sizeCtx[value.Var]
|
||||
if !ok {
|
||||
return 0, errors.New("unknown variable: ", value.Var)
|
||||
}
|
||||
return length, nil
|
||||
case *ExprArg_Metadata:
|
||||
return 0, errors.New("metadata not implemented: ", value.Metadata)
|
||||
case *ExprArg_Expr:
|
||||
return measureExpr(value.Expr, sizeCtx)
|
||||
default:
|
||||
return 0, errors.New("empty expr arg")
|
||||
}
|
||||
}
|
||||
|
||||
func (v evalValue) asBytes() ([]byte, error) {
|
||||
if v.bytes != nil {
|
||||
return append([]byte(nil), v.bytes...), nil
|
||||
}
|
||||
return nil, errors.New("expr value is not bytes")
|
||||
}
|
||||
|
||||
func (v evalValue) asU64() (uint64, error) {
|
||||
if v.u64 != nil {
|
||||
return *v.u64, nil
|
||||
}
|
||||
return 0, errors.New("expr value is not u64")
|
||||
}
|
||||
|
||||
func sizeMapFromEvalContext(ctx *evalContext) map[string]int {
|
||||
sizes := make(map[string]int, len(ctx.vars))
|
||||
for key, value := range ctx.vars {
|
||||
sizes[key] = len(value)
|
||||
}
|
||||
return sizes
|
||||
}
|
||||
|
||||
func loadMetadata(dst map[string]evalValue, prefix string, addr net.Addr) {
|
||||
if addr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch value := addr.(type) {
|
||||
case *net.UDPAddr:
|
||||
loadIPPortMetadata(dst, prefix, value.IP, value.Port)
|
||||
case *net.TCPAddr:
|
||||
loadIPPortMetadata(dst, prefix, value.IP, value.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func loadIPPortMetadata(dst map[string]evalValue, prefix string, ip net.IP, port int) {
|
||||
portValue := uint64(port)
|
||||
dst[prefix+"_port"] = evalValue{u64: &portValue}
|
||||
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
ipValue := uint64(binary.BigEndian.Uint32(ip4))
|
||||
dst[prefix+"_ip4_u32"] = evalValue{u64: &ipValue}
|
||||
}
|
||||
}
|
||||
130
transport/internet/finalmask/header/custom/evaluator_test.go
Normal file
130
transport/internet/finalmask/header/custom/evaluator_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvaluatorSaveAndReuseWithinPacket(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Rand: 4,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := bytes.Repeat([]byte{0x2A}, 8)
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorSliceReturnsWindow(t *testing.T) {
|
||||
sequence := &TCPSequence{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "slice",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{1, 2, 3, 4}}},
|
||||
{Value: &ExprArg_U64{U64: 1}},
|
||||
{Value: &ExprArg_U64{U64: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateTCPSequence(sequence)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(got, []byte{2, 3}) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorConcatPreservesOrder(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte("ab")}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte("cd")}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte("ef")}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(got) != "abcdef" {
|
||||
t.Fatalf("unexpected output: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorBeXorProducesExpectedBytes(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{
|
||||
Value: &ExprArg_Expr{
|
||||
Expr: &Expr{
|
||||
Op: "xor16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_U64{U64: 0x1234}},
|
||||
{Value: &ExprArg_U64{U64: 0xFFFF}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := evaluateUDPItems(items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(got, []byte{0xED, 0xCB}) {
|
||||
t.Fatalf("unexpected output: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluatorRejectsInvalidArgType(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0x01}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := evaluateUDPItems(items)
|
||||
if err == nil {
|
||||
t.Fatal("expected evaluator error")
|
||||
}
|
||||
}
|
||||
210
transport/internet/finalmask/header/custom/metadata_test.go
Normal file
210
transport/internet/finalmask/header/custom/metadata_test.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||
)
|
||||
|
||||
func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
|
||||
items := []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "nope"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := evaluateUDPItemsWithContext(items, newEvalContext())
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown metadata") {
|
||||
t.Fatalf("expected unknown metadata error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataUDPWriteUsesRemotePort(t *testing.T) {
|
||||
cfg := &UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg}).WrapPacketConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
payload := []byte("meta")
|
||||
if _, err := client.WriteTo(payload, serverRaw.LocalAddr()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wire := make([]byte, 64)
|
||||
_ = serverRaw.SetDeadline(time.Now().Add(time.Second))
|
||||
n, _, err := serverRaw.ReadFrom(wire)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(payload)+2 {
|
||||
t.Fatalf("unexpected wire size: %d", n)
|
||||
}
|
||||
wantPort := uint16(serverRaw.LocalAddr().(*net.UDPAddr).Port)
|
||||
if got := binary.BigEndian.Uint16(wire[:2]); got != wantPort {
|
||||
t.Fatalf("unexpected encoded port: got=%d want=%d", got, wantPort)
|
||||
}
|
||||
if !bytes.Equal(wire[2:n], payload) {
|
||||
t.Fatalf("unexpected payload: %q", wire[2:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataTCPHandshakeUsesEndpointPorts(t *testing.T) {
|
||||
clientCfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "local_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
serverCfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "local_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "be16",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Metadata{Metadata: "remote_port"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
serverRawCh := make(chan net.Conn, 1)
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
serverRawCh <- conn
|
||||
}()
|
||||
|
||||
clientRaw, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
var serverRaw net.Conn
|
||||
select {
|
||||
case serverRaw = <-serverRawCh:
|
||||
case err := <-errCh:
|
||||
t.Fatal(err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("accept timeout")
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := clientCfg.WrapConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := serverCfg.WrapConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := client.Write([]byte("meta"))
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
buf := make([]byte, 4)
|
||||
if _, err := io.ReadFull(server, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(buf, []byte("meta")) {
|
||||
t.Fatalf("unexpected payload: %q", buf)
|
||||
}
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
57
transport/internet/finalmask/header/custom/state.go
Normal file
57
transport/internet/finalmask/header/custom/state.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stateEntry struct {
|
||||
vars map[string][]byte
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
type stateStore struct {
|
||||
mu sync.Mutex
|
||||
ttl time.Duration
|
||||
entries map[string]stateEntry
|
||||
}
|
||||
|
||||
func newStateStore(ttl time.Duration) *stateStore {
|
||||
return &stateStore{
|
||||
ttl: ttl,
|
||||
entries: make(map[string]stateEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stateStore) get(key string) (map[string][]byte, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
entry, ok := s.entries[key]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) {
|
||||
delete(s.entries, key)
|
||||
return nil, false
|
||||
}
|
||||
return cloneVars(entry.vars), true
|
||||
}
|
||||
|
||||
func (s *stateStore) set(key string, vars map[string][]byte) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.entries[key] = stateEntry{
|
||||
vars: cloneVars(vars),
|
||||
expiresAt: time.Now().Add(s.ttl),
|
||||
}
|
||||
}
|
||||
|
||||
func cloneVars(vars map[string][]byte) map[string][]byte {
|
||||
cloned := make(map[string][]byte, len(vars))
|
||||
for key, value := range vars {
|
||||
cloned[key] = append([]byte(nil), value...)
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
105
transport/internet/finalmask/header/custom/state_test.go
Normal file
105
transport/internet/finalmask/header/custom/state_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask"
|
||||
)
|
||||
|
||||
func mustSendRecvUDP(t *testing.T, from net.PacketConn, to net.PacketConn, msg []byte) {
|
||||
t.Helper()
|
||||
|
||||
go func() {
|
||||
_, err := from.WriteTo(msg, to.LocalAddr())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, _, err := to.ReadFrom(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(msg) {
|
||||
t.Fatalf("unexpected size: %d", n)
|
||||
}
|
||||
if !bytes.Equal(buf[:n], msg) {
|
||||
t.Fatalf("unexpected payload: %q", buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateUDPResponseReusesPriorCapturedValues(t *testing.T) {
|
||||
cfg := &UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
},
|
||||
Server: []*UDPItem{
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
}
|
||||
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
|
||||
|
||||
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := maskManager.WrapPacketConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := maskManager.WrapPacketConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(2 * time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(2 * time.Second))
|
||||
|
||||
mustSendRecvUDP(t, client, server, []byte("client->server"))
|
||||
mustSendRecvUDP(t, server, client, []byte("server->client"))
|
||||
}
|
||||
|
||||
func TestStateStoreIsolatesKeys(t *testing.T) {
|
||||
store := newStateStore(5 * time.Second)
|
||||
store.set("a", map[string][]byte{"txid": {0x01}})
|
||||
store.set("b", map[string][]byte{"txid": {0x02}})
|
||||
|
||||
varsA, ok := store.get("a")
|
||||
if !ok || len(varsA["txid"]) != 1 || varsA["txid"][0] != 0x01 {
|
||||
t.Fatalf("unexpected vars for key a: %v", varsA)
|
||||
}
|
||||
varsB, ok := store.get("b")
|
||||
if !ok || len(varsB["txid"]) != 1 || varsB["txid"][0] != 0x02 {
|
||||
t.Fatalf("unexpected vars for key b: %v", varsB)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateStoreExpiresEntries(t *testing.T) {
|
||||
store := newStateStore(10 * time.Millisecond)
|
||||
store.set("a", map[string][]byte{"txid": {0x01}})
|
||||
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
|
||||
if _, ok := store.get("a"); ok {
|
||||
t.Fatal("expected expired state entry to be removed")
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
type tcpCustomClient struct {
|
||||
clients []*TCPSequence
|
||||
servers []*TCPSequence
|
||||
state *stateStore
|
||||
}
|
||||
|
||||
type tcpCustomClientConn struct {
|
||||
@@ -31,6 +32,7 @@ func NewConnClientTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
|
||||
header: &tcpCustomClient{
|
||||
clients: c.Clients,
|
||||
servers: c.Servers,
|
||||
state: newStateStore(5 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -63,16 +65,20 @@ func (c *tcpCustomClientConn) Read(p []byte) (n int, err error) {
|
||||
|
||||
func (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {
|
||||
c.once.Do(func() {
|
||||
ctx := newEvalContextWithAddrs(c.LocalAddr(), c.RemoteAddr())
|
||||
if vars, ok := c.header.state.get(tcpStateKey(c.LocalAddr(), c.RemoteAddr())); ok {
|
||||
ctx.vars = cloneVars(vars)
|
||||
}
|
||||
i := 0
|
||||
j := 0
|
||||
for i = range c.header.clients {
|
||||
if !writeSequence(c.Conn, c.header.clients[i]) {
|
||||
if !writeSequenceWithContext(c.Conn, c.header.clients[i], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
if j < len(c.header.servers) {
|
||||
if !readSequence(c.Conn, c.header.servers[j]) {
|
||||
if !readSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
@@ -81,13 +87,14 @@ func (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
for j < len(c.header.servers) {
|
||||
if !readSequence(c.Conn, c.header.servers[j]) {
|
||||
if !readSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
c.header.state.set(tcpStateKey(c.LocalAddr(), c.RemoteAddr()), ctx.vars)
|
||||
c.auth = true
|
||||
c.wg.Done()
|
||||
})
|
||||
@@ -105,6 +112,7 @@ type tcpCustomServer struct {
|
||||
clients []*TCPSequence
|
||||
servers []*TCPSequence
|
||||
errors []*TCPSequence
|
||||
state *stateStore
|
||||
}
|
||||
|
||||
type tcpCustomServerConn struct {
|
||||
@@ -123,6 +131,7 @@ func NewConnServerTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
|
||||
clients: c.Clients,
|
||||
servers: c.Servers,
|
||||
errors: c.Errors,
|
||||
state: newStateStore(5 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -145,19 +154,23 @@ func (c *tcpCustomServerConn) Splice() bool {
|
||||
|
||||
func (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {
|
||||
c.once.Do(func() {
|
||||
ctx := newEvalContextWithAddrs(c.LocalAddr(), c.RemoteAddr())
|
||||
if vars, ok := c.header.state.get(tcpStateKey(c.LocalAddr(), c.RemoteAddr())); ok {
|
||||
ctx.vars = cloneVars(vars)
|
||||
}
|
||||
i := 0
|
||||
j := 0
|
||||
for i = range c.header.clients {
|
||||
if !readSequence(c.Conn, c.header.clients[i]) {
|
||||
if !readSequenceWithContext(c.Conn, c.header.clients[i], ctx) {
|
||||
if i < len(c.header.errors) {
|
||||
writeSequence(c.Conn, c.header.errors[i])
|
||||
writeSequenceWithContext(c.Conn, c.header.errors[i], ctx)
|
||||
}
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
if j < len(c.header.servers) {
|
||||
if !writeSequence(c.Conn, c.header.servers[j]) {
|
||||
if !writeSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
@@ -166,13 +179,14 @@ func (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
for j < len(c.header.servers) {
|
||||
if !writeSequence(c.Conn, c.header.servers[j]) {
|
||||
if !writeSequenceWithContext(c.Conn, c.header.servers[j], ctx) {
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
c.header.state.set(tcpStateKey(c.LocalAddr(), c.RemoteAddr()), ctx.vars)
|
||||
c.auth = true
|
||||
c.wg.Done()
|
||||
})
|
||||
@@ -197,24 +211,56 @@ func (c *tcpCustomServerConn) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func readSequence(r io.Reader, sequence *TCPSequence) bool {
|
||||
return readSequenceWithContext(r, sequence, newEvalContext())
|
||||
}
|
||||
|
||||
func readSequenceWithContext(r io.Reader, sequence *TCPSequence, ctx *evalContext) bool {
|
||||
for _, item := range sequence.Sequence {
|
||||
length := max(int(item.Rand), len(item.Packet))
|
||||
length, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeMapFromEvalContext(ctx))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
n, err := io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if item.Rand > 0 && n != length {
|
||||
if n != length {
|
||||
return false
|
||||
}
|
||||
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, buf[:n]) {
|
||||
return false
|
||||
switch {
|
||||
case item.Rand > 0:
|
||||
case len(item.Packet) > 0:
|
||||
if !bytes.Equal(item.Packet, buf[:n]) {
|
||||
return false
|
||||
}
|
||||
case item.Var != "":
|
||||
saved, ok := ctx.vars[item.Var]
|
||||
if !ok || !bytes.Equal(saved, buf[:n]) {
|
||||
return false
|
||||
}
|
||||
case item.Expr != nil:
|
||||
evaluated, err := evaluateExpr(item.Expr, ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
expected, err := evaluated.asBytes()
|
||||
if err != nil || !bytes.Equal(expected, buf[:n]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if item.Save != "" {
|
||||
ctx.vars[item.Save] = append([]byte(nil), buf[:n]...)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func writeSequence(w io.Writer, sequence *TCPSequence) bool {
|
||||
return writeSequenceWithContext(w, sequence, newEvalContext())
|
||||
}
|
||||
|
||||
func writeSequenceWithContext(w io.Writer, sequence *TCPSequence, ctx *evalContext) bool {
|
||||
var merged []byte
|
||||
for _, item := range sequence.Sequence {
|
||||
if item.DelayMax > 0 {
|
||||
@@ -227,13 +273,11 @@ func writeSequence(w io.Writer, sequence *TCPSequence) bool {
|
||||
}
|
||||
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
|
||||
}
|
||||
if item.Rand > 0 {
|
||||
buf := make([]byte, item.Rand)
|
||||
crypto.RandBytesBetween(buf, byte(item.RandMin), byte(item.RandMax))
|
||||
merged = append(merged, buf...)
|
||||
} else {
|
||||
merged = append(merged, item.Packet...)
|
||||
evaluated, err := evaluateItem(item.Rand, item.RandMin, item.RandMax, item.Packet, item.Save, item.Var, item.Expr, ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
merged = append(merged, evaluated...)
|
||||
}
|
||||
if len(merged) > 0 {
|
||||
_, err := w.Write(merged)
|
||||
@@ -244,3 +288,15 @@ func writeSequence(w io.Writer, sequence *TCPSequence) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func tcpStateKey(local, remote net.Addr) string {
|
||||
localKey := ""
|
||||
if local != nil {
|
||||
localKey = local.String()
|
||||
}
|
||||
remoteKey := ""
|
||||
if remote != nil {
|
||||
remoteKey = remote.String()
|
||||
}
|
||||
return localKey + "|" + remoteKey
|
||||
}
|
||||
|
||||
150
transport/internet/finalmask/header/custom/tcp_runtime_test.go
Normal file
150
transport/internet/finalmask/header/custom/tcp_runtime_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDSLTCPHandshakeReusesCapturedValue(t *testing.T) {
|
||||
cfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientRaw, serverRaw := net.Pipe()
|
||||
defer clientRaw.Close()
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := cfg.WrapConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := cfg.WrapConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := client.Write([]byte("payload"))
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
buf := make([]byte, len("payload"))
|
||||
if _, err := io.ReadFull(server, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(buf) != "payload" {
|
||||
t.Fatalf("unexpected payload: %q", buf)
|
||||
}
|
||||
if err := <-writeErr; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSLTCPClientRejectsMismatchedResponseSequence(t *testing.T) {
|
||||
clientCfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
serverCfg := &TCPConfig{
|
||||
Clients: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
Save: "txid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*TCPSequence{
|
||||
{
|
||||
Sequence: []*TCPItem{
|
||||
{
|
||||
Packet: []byte{0x01, 0x02},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientRaw, serverRaw := net.Pipe()
|
||||
defer clientRaw.Close()
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := clientCfg.WrapConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := serverCfg.WrapConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := client.Write([]byte("payload"))
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
buf := make([]byte, len("payload"))
|
||||
_, readErr := server.Read(buf)
|
||||
|
||||
if err := <-writeErr; err == nil || !strings.Contains(err.Error(), "header auth failed") {
|
||||
t.Fatalf("expected client auth failure, got %v", err)
|
||||
}
|
||||
if readErr == nil {
|
||||
t.Fatal("expected server read to fail")
|
||||
}
|
||||
if ne, ok := readErr.(net.Error); !ok || !ne.Timeout() {
|
||||
t.Fatalf("expected server timeout after client auth failure, got %v", readErr)
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package custom
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
@@ -12,41 +12,34 @@ type udpCustomClient struct {
|
||||
client []*UDPItem
|
||||
server []*UDPItem
|
||||
merged []byte
|
||||
read int
|
||||
addr net.Addr
|
||||
state *stateStore
|
||||
vars map[string][]byte
|
||||
}
|
||||
|
||||
func (h *udpCustomClient) Serialize(b []byte) {
|
||||
index := 0
|
||||
for _, item := range h.client {
|
||||
if item.Rand > 0 {
|
||||
crypto.RandBytesBetween(h.merged[index:index+int(item.Rand)], byte(item.RandMin), byte(item.RandMax))
|
||||
index += int(item.Rand)
|
||||
} else {
|
||||
index += len(item.Packet)
|
||||
}
|
||||
evaluated, err := evaluateUDPItems(h.client)
|
||||
if err != nil || len(evaluated) != len(h.merged) {
|
||||
copy(b, h.merged)
|
||||
return
|
||||
}
|
||||
copy(b, h.merged)
|
||||
copy(b, evaluated)
|
||||
}
|
||||
|
||||
func (h *udpCustomClient) Match(b []byte) bool {
|
||||
if len(b) < len(h.merged) {
|
||||
return false
|
||||
var initial map[string][]byte
|
||||
if h.state != nil {
|
||||
initial, _ = h.state.get(udpStateKey(h.addr))
|
||||
}
|
||||
|
||||
data := b
|
||||
match := true
|
||||
|
||||
for _, item := range h.server {
|
||||
length := max(int(item.Rand), len(item.Packet))
|
||||
|
||||
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
|
||||
match = false
|
||||
break
|
||||
vars, ok := matchUDPItems(h.server, b, h.read, initial)
|
||||
if ok {
|
||||
h.vars = vars
|
||||
if h.state != nil {
|
||||
h.state.set(udpStateKey(h.addr), vars)
|
||||
}
|
||||
|
||||
data = data[length:]
|
||||
}
|
||||
|
||||
return match
|
||||
return ok
|
||||
}
|
||||
|
||||
type udpCustomClientConn struct {
|
||||
@@ -60,18 +53,19 @@ func NewConnClientUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error)
|
||||
header: &udpCustomClient{
|
||||
client: c.Client,
|
||||
server: c.Server,
|
||||
state: newStateStore(5 * time.Second),
|
||||
vars: make(map[string][]byte),
|
||||
},
|
||||
}
|
||||
|
||||
index := 0
|
||||
for _, item := range conn.header.client {
|
||||
if item.Rand > 0 {
|
||||
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
|
||||
index += int(item.Rand)
|
||||
} else {
|
||||
conn.header.merged = append(conn.header.merged, item.Packet...)
|
||||
index += len(item.Packet)
|
||||
}
|
||||
clientSavedSizes := collectSavedUDPSizes(conn.header.client)
|
||||
size, err := measureUDPItems(conn.header.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.header.merged = make([]byte, size)
|
||||
conn.header.read, err = measureUDPItemsWithFallback(conn.header.server, clientSavedSizes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
@@ -86,54 +80,69 @@ func (c *udpCustomClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err erro
|
||||
return 0, addr, errors.New("header mismatch")
|
||||
}
|
||||
|
||||
return len(p) - len(c.header.merged), addr, nil
|
||||
return len(p) - c.header.read, addr, nil
|
||||
}
|
||||
|
||||
func (c *udpCustomClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.header.Serialize(p)
|
||||
var localAddr net.Addr
|
||||
if c.PacketConn != nil {
|
||||
localAddr = c.PacketConn.LocalAddr()
|
||||
}
|
||||
ctx := newEvalContextWithAddrs(localAddr, addr)
|
||||
if vars, ok := c.header.state.get(udpStateKey(addr)); ok {
|
||||
ctx.vars = cloneVars(vars)
|
||||
} else if len(c.header.vars) > 0 {
|
||||
ctx.vars = cloneVars(c.header.vars)
|
||||
}
|
||||
evaluated, err := evaluateUDPItemsWithContext(c.header.client, ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(evaluated) != len(c.header.merged) {
|
||||
return 0, errors.New("header size mismatch")
|
||||
}
|
||||
c.header.state.set(udpStateKey(addr), ctx.vars)
|
||||
copy(p, evaluated)
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *udpCustomClientConn) SetReadAddr(addr net.Addr) {
|
||||
c.header.addr = addr
|
||||
}
|
||||
|
||||
type udpCustomServer struct {
|
||||
client []*UDPItem
|
||||
server []*UDPItem
|
||||
merged []byte
|
||||
read int
|
||||
addr net.Addr
|
||||
state *stateStore
|
||||
vars map[string][]byte
|
||||
}
|
||||
|
||||
func (h *udpCustomServer) Serialize(b []byte) {
|
||||
index := 0
|
||||
for _, item := range h.server {
|
||||
if item.Rand > 0 {
|
||||
crypto.RandBytesBetween(h.merged[index:index+int(item.Rand)], byte(item.RandMin), byte(item.RandMax))
|
||||
index += int(item.Rand)
|
||||
} else {
|
||||
index += len(item.Packet)
|
||||
}
|
||||
evaluated, err := evaluateUDPItems(h.server)
|
||||
if err != nil || len(evaluated) != len(h.merged) {
|
||||
copy(b, h.merged)
|
||||
return
|
||||
}
|
||||
copy(b, h.merged)
|
||||
copy(b, evaluated)
|
||||
}
|
||||
|
||||
func (h *udpCustomServer) Match(b []byte) bool {
|
||||
if len(b) < len(h.merged) {
|
||||
return false
|
||||
var initial map[string][]byte
|
||||
if h.state != nil {
|
||||
initial, _ = h.state.get(udpStateKey(h.addr))
|
||||
}
|
||||
|
||||
data := b
|
||||
match := true
|
||||
|
||||
for _, item := range h.client {
|
||||
length := max(int(item.Rand), len(item.Packet))
|
||||
|
||||
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
|
||||
match = false
|
||||
break
|
||||
vars, ok := matchUDPItems(h.client, b, h.read, initial)
|
||||
if ok {
|
||||
h.vars = vars
|
||||
if h.state != nil {
|
||||
h.state.set(udpStateKey(h.addr), vars)
|
||||
}
|
||||
|
||||
data = data[length:]
|
||||
}
|
||||
|
||||
return match
|
||||
return ok
|
||||
}
|
||||
|
||||
type udpCustomServerConn struct {
|
||||
@@ -147,18 +156,19 @@ func NewConnServerUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error)
|
||||
header: &udpCustomServer{
|
||||
client: c.Client,
|
||||
server: c.Server,
|
||||
state: newStateStore(5 * time.Second),
|
||||
vars: make(map[string][]byte),
|
||||
},
|
||||
}
|
||||
|
||||
index := 0
|
||||
for _, item := range conn.header.server {
|
||||
if item.Rand > 0 {
|
||||
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
|
||||
index += int(item.Rand)
|
||||
} else {
|
||||
conn.header.merged = append(conn.header.merged, item.Packet...)
|
||||
index += len(item.Packet)
|
||||
}
|
||||
clientSavedSizes := collectSavedUDPSizes(conn.header.client)
|
||||
size, err := measureUDPItemsWithFallback(conn.header.server, clientSavedSizes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.header.merged = make([]byte, size)
|
||||
conn.header.read, err = measureUDPItems(conn.header.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
@@ -173,11 +183,87 @@ func (c *udpCustomServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err erro
|
||||
return 0, addr, errors.New("header mismatch")
|
||||
}
|
||||
|
||||
return len(p) - len(c.header.merged), addr, nil
|
||||
return len(p) - c.header.read, addr, nil
|
||||
}
|
||||
|
||||
func (c *udpCustomServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.header.Serialize(p)
|
||||
var localAddr net.Addr
|
||||
if c.PacketConn != nil {
|
||||
localAddr = c.PacketConn.LocalAddr()
|
||||
}
|
||||
ctx := newEvalContextWithAddrs(localAddr, addr)
|
||||
if vars, ok := c.header.state.get(udpStateKey(addr)); ok {
|
||||
ctx.vars = cloneVars(vars)
|
||||
} else if len(c.header.vars) > 0 {
|
||||
ctx.vars = cloneVars(c.header.vars)
|
||||
}
|
||||
evaluated, err := evaluateUDPItemsWithContext(c.header.server, ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(evaluated) != len(c.header.merged) {
|
||||
return 0, errors.New("header size mismatch")
|
||||
}
|
||||
c.header.state.set(udpStateKey(addr), ctx.vars)
|
||||
copy(p, evaluated)
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *udpCustomServerConn) SetReadAddr(addr net.Addr) {
|
||||
c.header.addr = addr
|
||||
}
|
||||
|
||||
func matchUDPItems(items []*UDPItem, data []byte, totalSize int, initial map[string][]byte) (map[string][]byte, bool) {
|
||||
if len(data) < totalSize {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ctx := newEvalContext()
|
||||
ctx.vars = cloneVars(initial)
|
||||
offset := 0
|
||||
for _, item := range items {
|
||||
length, err := measureItem(item.Rand, item.Packet, item.Save, item.Var, item.Expr, sizeMapFromEvalContext(ctx))
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(data[offset:]) < length {
|
||||
return nil, false
|
||||
}
|
||||
segment := append([]byte(nil), data[offset:offset+length]...)
|
||||
switch {
|
||||
case item.Rand > 0:
|
||||
case len(item.Packet) > 0:
|
||||
if !bytes.Equal(item.Packet, segment) {
|
||||
return nil, false
|
||||
}
|
||||
case item.Var != "":
|
||||
saved, ok := ctx.vars[item.Var]
|
||||
if !ok || !bytes.Equal(saved, segment) {
|
||||
return nil, false
|
||||
}
|
||||
case item.Expr != nil:
|
||||
evaluated, err := evaluateExpr(item.Expr, ctx)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
expected, err := evaluated.asBytes()
|
||||
if err != nil || !bytes.Equal(expected, segment) {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if item.Save != "" {
|
||||
ctx.vars[item.Save] = segment
|
||||
}
|
||||
offset += length
|
||||
}
|
||||
|
||||
return ctx.vars, true
|
||||
}
|
||||
|
||||
func udpStateKey(addr net.Addr) string {
|
||||
if addr == nil {
|
||||
return ""
|
||||
}
|
||||
return addr.String()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package custom
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDSLUDPClientSizeTracksEvaluatedItems(t *testing.T) {
|
||||
conn, err := NewConnClientUDP(&UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
RandMin: 0x2A,
|
||||
RandMax: 0x2A,
|
||||
Save: "txid",
|
||||
},
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
{
|
||||
Expr: &Expr{
|
||||
Op: "concat",
|
||||
Args: []*ExprArg{
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xAB}}},
|
||||
{Value: &ExprArg_Bytes{Bytes: []byte{0xCD}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got := conn.(*udpCustomClientConn).Size(); got != 6 {
|
||||
t.Fatalf("unexpected header size: got=%d want=6", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSLUDPServerMatchCapturesSavedValues(t *testing.T) {
|
||||
conn, err := NewConnServerUDP(&UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
Save: "txid",
|
||||
},
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := conn.(*udpCustomServerConn)
|
||||
if !server.header.Match([]byte{0x01, 0x02, 0x01, 0x02}) {
|
||||
t.Fatal("expected packet to match")
|
||||
}
|
||||
|
||||
if got := string(server.header.vars["txid"]); got != string([]byte{0x01, 0x02}) {
|
||||
t.Fatalf("unexpected saved txid: %x", server.header.vars["txid"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSLUDPServerRejectsMalformedVarReference(t *testing.T) {
|
||||
conn, err := NewConnServerUDP(&UDPConfig{
|
||||
Client: []*UDPItem{
|
||||
{
|
||||
Rand: 2,
|
||||
Save: "txid",
|
||||
},
|
||||
{
|
||||
Var: "txid",
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := conn.(*udpCustomServerConn)
|
||||
if server.header.Match([]byte{0x01, 0x02, 0x03, 0x04}) {
|
||||
t.Fatal("expected packet mismatch")
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -121,3 +122,128 @@ func TestConnReadWrite(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTCPcustomStaticHandshakeRoundTrip(t *testing.T) {
|
||||
cfg := &custom.TCPConfig{
|
||||
Clients: []*custom.TCPSequence{
|
||||
{
|
||||
Sequence: []*custom.TCPItem{
|
||||
{Packet: []byte("cli")},
|
||||
{Rand: 2, RandMin: 0x10, RandMax: 0x20},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*custom.TCPSequence{
|
||||
{
|
||||
Sequence: []*custom.TCPItem{
|
||||
{Packet: []byte("srv")},
|
||||
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
maskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{cfg})
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
clientRaw, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
serverRaw, err := ln.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := maskManager.WrapConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := maskManager.WrapConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
mustSendRecvTcp(t, client, server, []byte("custom tcp payload"))
|
||||
mustSendRecvTcp(t, server, client, []byte("custom tcp response"))
|
||||
}
|
||||
|
||||
func TestTCPcustomClientRejectsMismatchedServerSequence(t *testing.T) {
|
||||
clientCfg := &custom.TCPConfig{
|
||||
Clients: []*custom.TCPSequence{
|
||||
{
|
||||
Sequence: []*custom.TCPItem{
|
||||
{Packet: []byte{0x01}},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*custom.TCPSequence{
|
||||
{
|
||||
Sequence: []*custom.TCPItem{
|
||||
{Packet: []byte{0x02}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
serverCfg := &custom.TCPConfig{
|
||||
Clients: []*custom.TCPSequence{
|
||||
{
|
||||
Sequence: []*custom.TCPItem{
|
||||
{Packet: []byte{0x01}},
|
||||
},
|
||||
},
|
||||
},
|
||||
Servers: []*custom.TCPSequence{
|
||||
{
|
||||
Sequence: []*custom.TCPItem{
|
||||
{Packet: []byte{0x03}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
clientRaw, serverRaw := net.Pipe()
|
||||
defer clientRaw.Close()
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := clientCfg.WrapConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := serverCfg.WrapConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = client.SetDeadline(time.Now().Add(time.Second))
|
||||
_ = server.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
writeErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := client.Write([]byte("boom"))
|
||||
writeErr <- err
|
||||
}()
|
||||
|
||||
buf := make([]byte, 4)
|
||||
_, readErr := server.Read(buf)
|
||||
|
||||
if err := <-writeErr; err == nil || !strings.Contains(err.Error(), "header auth failed") {
|
||||
t.Fatalf("expected client header auth failure, got %v", err)
|
||||
}
|
||||
if readErr == nil {
|
||||
t.Fatal("expected server read to fail")
|
||||
}
|
||||
if ne, ok := readErr.(net.Error); !ok || !ne.Timeout() {
|
||||
t.Fatalf("expected server timeout after client auth failure, got %v", readErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +215,108 @@ func TestPacketConnReadWrite(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUDPcustomStaticHeaderWireShape(t *testing.T) {
|
||||
cfg := &custom.UDPConfig{
|
||||
Client: []*custom.UDPItem{
|
||||
{Packet: []byte{0xAA, 0xBB}},
|
||||
{Rand: 2, RandMin: 0x10, RandMax: 0x20},
|
||||
},
|
||||
Server: []*custom.UDPItem{
|
||||
{Packet: []byte{0xCC}},
|
||||
{Rand: 1, RandMin: 0x30, RandMax: 0x40},
|
||||
},
|
||||
}
|
||||
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
|
||||
|
||||
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
client, err := maskManager.WrapPacketConnClient(clientRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
payload := []byte("udp-custom-wire")
|
||||
if _, err := client.WriteTo(payload, serverRaw.LocalAddr()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
_ = serverRaw.SetDeadline(time.Now().Add(time.Second))
|
||||
n, _, err := serverRaw.ReadFrom(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n != len(payload)+4 {
|
||||
t.Fatalf("unexpected wire size: got=%d want=%d", n, len(payload)+4)
|
||||
}
|
||||
if !bytes.Equal(buf[:2], []byte{0xAA, 0xBB}) {
|
||||
t.Fatalf("unexpected static header prefix: %x", buf[:2])
|
||||
}
|
||||
for i, b := range buf[2:4] {
|
||||
if b < 0x10 || b > 0x20 {
|
||||
t.Fatalf("rand byte %d out of range: %x", i, b)
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(buf[4:n], payload) {
|
||||
t.Fatalf("unexpected payload: %q", buf[4:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUDPcustomServerRejectsMismatchedStaticHeader(t *testing.T) {
|
||||
cfg := &custom.UDPConfig{
|
||||
Client: []*custom.UDPItem{
|
||||
{Packet: []byte{0x01, 0x02}},
|
||||
},
|
||||
Server: []*custom.UDPItem{
|
||||
{Packet: []byte{0x03}},
|
||||
},
|
||||
}
|
||||
maskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})
|
||||
|
||||
clientRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
serverRaw, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer serverRaw.Close()
|
||||
|
||||
server, err := maskManager.WrapPacketConnServer(serverRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = server.SetDeadline(time.Now().Add(200 * time.Millisecond))
|
||||
|
||||
if _, err := clientRaw.WriteTo([]byte{0x09, 0x09, 'b', 'a', 'd'}, server.LocalAddr()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 128)
|
||||
n, _, err := server.ReadFrom(buf)
|
||||
if n != 0 {
|
||||
t.Fatalf("expected no payload on mismatched header, got %d bytes", n)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("expected mismatch to be dropped without surfaced error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSudokuBDD(t *testing.T) {
|
||||
t.Run("GivenSudokuTCPMask_WhenRoundTripWithAsciiPreference_ThenPayloadMatches", func(t *testing.T) {
|
||||
cfg := &sudoku.Config{
|
||||
|
||||
Reference in New Issue
Block a user