Иван
2026-04-15 21:17:51 +07:00
committed by GitHub
parent 6780045550
commit 175502d807
16 changed files with 2275 additions and 150 deletions

View File

@@ -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

View File

@@ -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,
},

View File

@@ -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;
}
}

View 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}
}
}

View 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")
}
}

View 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)
}
}

View 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
}

View 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")
}
}

View File

@@ -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
}

View 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)
}
}

View File

@@ -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()
}

View File

@@ -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")
}
}

View File

@@ -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)
}
}

View File

@@ -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{