mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-02 05:46:39 +00:00
Noise finalmask: type supports "exp" (#6862)
https://github.com/XTLS/Xray-core/pull/6844#issuecomment-5859092964 https://github.com/XTLS/Xray-core/pull/6844#issuecomment-5859855674 https://github.com/XTLS/Xray-core/pull/6862#issuecomment-5897471145
This commit is contained in:
@@ -310,14 +310,27 @@ type NoiseMask struct {
|
||||
}
|
||||
|
||||
func (c *NoiseMask) Build() (proto.Message, error) {
|
||||
noiseSlice := make([]*noise.Item, 0, len(c.Noise))
|
||||
for _, item := range c.Noise {
|
||||
if len(item.Packet) > 0 && item.Rand.To > 0 {
|
||||
return nil, errors.New("len(item.Packet) > 0 && item.Rand.To > 0")
|
||||
}
|
||||
}
|
||||
|
||||
noiseSlice := make([]*noise.Item, 0, len(c.Noise))
|
||||
for _, item := range c.Noise {
|
||||
if strings.ToLower(item.Type) == "exp" {
|
||||
var exp string
|
||||
if err := json.Unmarshal(item.Packet, &exp); err != nil {
|
||||
return nil, errors.New(`"packet" of noise "type": "exp" must be a string`).Base(err)
|
||||
}
|
||||
segments, err := parseNoiseExp(exp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
noiseSlice = append(noiseSlice, &noise.Item{
|
||||
Segments: segments,
|
||||
DelayMin: int64(item.Delay.From),
|
||||
DelayMax: int64(item.Delay.To),
|
||||
})
|
||||
continue
|
||||
}
|
||||
if item.RandRange == nil {
|
||||
item.RandRange = &Int32Range{From: 0, To: 255}
|
||||
}
|
||||
@@ -346,6 +359,88 @@ func (c *NoiseMask) Build() (proto.Message, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
var noiseExpPattern = regexp.MustCompile(`<\s*([a-z]+)(?:\s+([^>]*?))?\s*>`)
|
||||
|
||||
func parseNoiseExp(exp string) ([]*noise.Segment, error) {
|
||||
var segments []*noise.Segment
|
||||
matches := noiseExpPattern.FindAllStringSubmatchIndex(exp, -1)
|
||||
last := 0
|
||||
for _, m := range matches {
|
||||
if strings.TrimSpace(exp[last:m[0]]) != "" {
|
||||
return nil, errors.New("invalid noise exp near ", exp[last:m[0]])
|
||||
}
|
||||
last = m[1]
|
||||
key := exp[m[2]:m[3]]
|
||||
arg := ""
|
||||
if m[4] >= 0 {
|
||||
arg = exp[m[4]:m[5]]
|
||||
}
|
||||
segment, err := buildNoiseSegment(key, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
segments = append(segments, segment)
|
||||
}
|
||||
if strings.TrimSpace(exp[last:]) != "" {
|
||||
return nil, errors.New("invalid noise exp near ", exp[last:])
|
||||
}
|
||||
if len(segments) == 0 {
|
||||
return nil, errors.New("empty noise exp: ", exp)
|
||||
}
|
||||
return segments, nil
|
||||
}
|
||||
|
||||
func buildNoiseSegment(key, arg string) (*noise.Segment, error) {
|
||||
sizeSegment := func(kind noise.Segment_Kind) (*noise.Segment, error) {
|
||||
if arg == "" {
|
||||
return nil, errors.New("<", key, "> in noise exp needs a size")
|
||||
}
|
||||
lo, hi, err := ParseRangeString(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lo < 0 || hi < lo || hi > 65535 {
|
||||
return nil, errors.New("invalid size in noise exp: ", arg)
|
||||
}
|
||||
return &noise.Segment{Kind: kind, MinSize: int64(lo), MaxSize: int64(hi)}, nil
|
||||
}
|
||||
switch key {
|
||||
case "b":
|
||||
hexStr := strings.TrimPrefix(strings.TrimPrefix(strings.Join(strings.Fields(arg), ""), "0x"), "0X")
|
||||
if len(hexStr) == 0 {
|
||||
return nil, errors.New("empty bytes in noise exp")
|
||||
}
|
||||
raw, err := hex.DecodeString(hexStr)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid hex in noise exp: ", arg).Base(err)
|
||||
}
|
||||
return &noise.Segment{Kind: noise.Segment_BYTES, Bytes: raw}, nil
|
||||
case "r":
|
||||
return sizeSegment(noise.Segment_RANDOM)
|
||||
case "rc":
|
||||
return sizeSegment(noise.Segment_RANDOM_ASCII)
|
||||
case "rd":
|
||||
return sizeSegment(noise.Segment_RANDOM_DIGIT)
|
||||
case "t":
|
||||
if arg != "" {
|
||||
return nil, errors.New("<t> in noise exp takes no argument")
|
||||
}
|
||||
return &noise.Segment{Kind: noise.Segment_TIMESTAMP}, nil
|
||||
case "c":
|
||||
if arg != "" {
|
||||
return nil, errors.New("<c> in noise exp takes no argument")
|
||||
}
|
||||
return &noise.Segment{Kind: noise.Segment_COUNTER}, nil
|
||||
case "n":
|
||||
if arg != "" {
|
||||
return nil, errors.New("<n> in noise exp takes no argument")
|
||||
}
|
||||
return &noise.Segment{Kind: noise.Segment_NONCE}, nil
|
||||
default:
|
||||
return nil, errors.New("unknown <", key, "> in noise exp")
|
||||
}
|
||||
}
|
||||
|
||||
type UDPItem struct {
|
||||
Rand int32 `json:"rand"`
|
||||
RandRange *Int32Range `json:"randRange"`
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
|
||||
)
|
||||
|
||||
func expPacket(exp string) json.RawMessage {
|
||||
b, _ := json.Marshal(exp)
|
||||
return b
|
||||
}
|
||||
|
||||
func buildNoiseExp(exp string) (*noise.Config, error) {
|
||||
msg, err := (&NoiseMask{Noise: []NoiseItem{{Type: "exp", Packet: expPacket(exp)}}}).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg.(*noise.Config), nil
|
||||
}
|
||||
|
||||
func TestNoiseExp(t *testing.T) {
|
||||
cfg, err := buildNoiseExp("<b 0d0a0d0a><t><r 24><rc 20-40><rd 8><c><n>")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
segments := cfg.Items[0].Segments
|
||||
if len(segments) != 7 {
|
||||
t.Fatalf("got %d segments, want 7", len(segments))
|
||||
}
|
||||
want := []struct {
|
||||
kind noise.Segment_Kind
|
||||
bytes []byte
|
||||
min, max int64
|
||||
}{
|
||||
{noise.Segment_BYTES, []byte{0x0d, 0x0a, 0x0d, 0x0a}, 0, 0},
|
||||
{noise.Segment_TIMESTAMP, nil, 0, 0},
|
||||
{noise.Segment_RANDOM, nil, 24, 24},
|
||||
{noise.Segment_RANDOM_ASCII, nil, 20, 40},
|
||||
{noise.Segment_RANDOM_DIGIT, nil, 8, 8},
|
||||
{noise.Segment_COUNTER, nil, 0, 0},
|
||||
{noise.Segment_NONCE, nil, 0, 0},
|
||||
}
|
||||
for i, w := range want {
|
||||
s := segments[i]
|
||||
if s.Kind != w.kind || s.MinSize != w.min || s.MaxSize != w.max || string(s.Bytes) != string(w.bytes) {
|
||||
t.Errorf("segment %d = %+v, want %+v", i, s, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoiseExpStripsHexPrefix(t *testing.T) {
|
||||
cfg, err := buildNoiseExp("<b 0x16030100>")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := cfg.Items[0].Segments[0].Bytes; string(got) != string([]byte{0x16, 0x03, 0x01, 0x00}) {
|
||||
t.Errorf("got %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoiseExpWhitespace(t *testing.T) {
|
||||
if _, err := buildNoiseExp(" <b 00> <t> "); err != nil {
|
||||
t.Errorf("surrounding whitespace should be allowed: %v", err)
|
||||
}
|
||||
cfg, err := buildNoiseExp("<b 0d 0a 0d 0a>")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := cfg.Items[0].Segments[0].Bytes; string(got) != "\r\n\r\n" {
|
||||
t.Errorf("got %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoiseExpRejects(t *testing.T) {
|
||||
for _, exp := range []string{
|
||||
"<x 1>",
|
||||
"<b>",
|
||||
"<b zz>",
|
||||
"<b 0d0>",
|
||||
"<r>",
|
||||
"<r -1>",
|
||||
"<r 40-20>",
|
||||
"<r 70000>",
|
||||
"<t 5>",
|
||||
"<n 5>",
|
||||
"garbage<t>",
|
||||
"<t> tail",
|
||||
"<t><b>",
|
||||
} {
|
||||
if _, err := buildNoiseExp(exp); err == nil {
|
||||
t.Errorf("expected an error for %q", exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoiseExpConflicts(t *testing.T) {
|
||||
if _, err := (&NoiseMask{Noise: []NoiseItem{{Type: "exp", Packet: expPacket("<t>"), Rand: Int32Range{From: 10, To: 20}}}}).Build(); err == nil {
|
||||
t.Error("exp with rand should be rejected")
|
||||
}
|
||||
for _, packet := range []string{``, `[1, 2]`, `5`} {
|
||||
if _, err := (&NoiseMask{Noise: []NoiseItem{{Type: "exp", Packet: json.RawMessage(packet)}}}).Build(); err == nil {
|
||||
t.Errorf("expected an error for packet %q", packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoiseExpFromJSON(t *testing.T) {
|
||||
var mask NoiseMask
|
||||
if err := json.Unmarshal([]byte(`{"noise": [
|
||||
{"type": "exp", "packet": "<b 504f5354><rd 10-20>", "delay": "1-3"},
|
||||
{"type": "EXP", "packet": "<t>"},
|
||||
{"type": "str", "packet": "<t>"},
|
||||
{"rand": "10-20"}
|
||||
]}`), &mask); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg, err := mask.Build()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
items := msg.(*noise.Config).Items
|
||||
if len(items[0].Segments) != 2 || items[0].DelayMin != 1 || items[0].DelayMax != 3 {
|
||||
t.Errorf("item 0 = %+v", items[0])
|
||||
}
|
||||
if len(items[1].Segments) != 1 || items[1].Segments[0].Kind != noise.Segment_TIMESTAMP {
|
||||
t.Errorf("item 1 = %+v", items[1])
|
||||
}
|
||||
if len(items[2].Segments) != 0 || string(items[2].Packet) != "<t>" {
|
||||
t.Errorf("item 2 = %+v", items[2])
|
||||
}
|
||||
if len(items[3].Segments) != 0 || items[3].RandMin != 10 || items[3].RandMax != 20 {
|
||||
t.Errorf("item 3 = %+v", items[3])
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,135 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Segment_Kind int32
|
||||
|
||||
const (
|
||||
Segment_BYTES Segment_Kind = 0
|
||||
Segment_RANDOM Segment_Kind = 1
|
||||
Segment_RANDOM_ASCII Segment_Kind = 2
|
||||
Segment_RANDOM_DIGIT Segment_Kind = 3
|
||||
Segment_TIMESTAMP Segment_Kind = 4
|
||||
Segment_COUNTER Segment_Kind = 5
|
||||
Segment_NONCE Segment_Kind = 6
|
||||
)
|
||||
|
||||
// Enum value maps for Segment_Kind.
|
||||
var (
|
||||
Segment_Kind_name = map[int32]string{
|
||||
0: "BYTES",
|
||||
1: "RANDOM",
|
||||
2: "RANDOM_ASCII",
|
||||
3: "RANDOM_DIGIT",
|
||||
4: "TIMESTAMP",
|
||||
5: "COUNTER",
|
||||
6: "NONCE",
|
||||
}
|
||||
Segment_Kind_value = map[string]int32{
|
||||
"BYTES": 0,
|
||||
"RANDOM": 1,
|
||||
"RANDOM_ASCII": 2,
|
||||
"RANDOM_DIGIT": 3,
|
||||
"TIMESTAMP": 4,
|
||||
"COUNTER": 5,
|
||||
"NONCE": 6,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Segment_Kind) Enum() *Segment_Kind {
|
||||
p := new(Segment_Kind)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Segment_Kind) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Segment_Kind) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_transport_internet_finalmask_noise_config_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Segment_Kind) Type() protoreflect.EnumType {
|
||||
return &file_transport_internet_finalmask_noise_config_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Segment_Kind) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Segment_Kind.Descriptor instead.
|
||||
func (Segment_Kind) EnumDescriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type Segment struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Kind Segment_Kind `protobuf:"varint,1,opt,name=kind,proto3,enum=xray.transport.internet.finalmask.noise.Segment_Kind" json:"kind,omitempty"`
|
||||
Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3" json:"bytes,omitempty"`
|
||||
MinSize int64 `protobuf:"varint,3,opt,name=min_size,json=minSize,proto3" json:"min_size,omitempty"`
|
||||
MaxSize int64 `protobuf:"varint,4,opt,name=max_size,json=maxSize,proto3" json:"max_size,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Segment) Reset() {
|
||||
*x = Segment{}
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Segment) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Segment) ProtoMessage() {}
|
||||
|
||||
func (x *Segment) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_noise_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 Segment.ProtoReflect.Descriptor instead.
|
||||
func (*Segment) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Segment) GetKind() Segment_Kind {
|
||||
if x != nil {
|
||||
return x.Kind
|
||||
}
|
||||
return Segment_BYTES
|
||||
}
|
||||
|
||||
func (x *Segment) GetBytes() []byte {
|
||||
if x != nil {
|
||||
return x.Bytes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Segment) GetMinSize() int64 {
|
||||
if x != nil {
|
||||
return x.MinSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Segment) GetMaxSize() int64 {
|
||||
if x != nil {
|
||||
return x.MaxSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RandMin int64 `protobuf:"varint,1,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
|
||||
@@ -30,13 +159,14 @@ type Item struct {
|
||||
Packet []byte `protobuf:"bytes,5,opt,name=packet,proto3" json:"packet,omitempty"`
|
||||
DelayMin int64 `protobuf:"varint,6,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
|
||||
DelayMax int64 `protobuf:"varint,7,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
|
||||
Segments []*Segment `protobuf:"bytes,8,rep,name=segments,proto3" json:"segments,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Item) Reset() {
|
||||
*x = Item{}
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[0]
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -48,7 +178,7 @@ func (x *Item) String() string {
|
||||
func (*Item) ProtoMessage() {}
|
||||
|
||||
func (x *Item) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[0]
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -61,7 +191,7 @@ func (x *Item) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Item.ProtoReflect.Descriptor instead.
|
||||
func (*Item) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{0}
|
||||
return file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Item) GetRandMin() int64 {
|
||||
@@ -113,6 +243,13 @@ func (x *Item) GetDelayMax() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Item) GetSegments() []*Segment {
|
||||
if x != nil {
|
||||
return x.Segments
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ResetMin int64 `protobuf:"varint,1,opt,name=reset_min,json=resetMin,proto3" json:"reset_min,omitempty"`
|
||||
@@ -124,7 +261,7 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[1]
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -136,7 +273,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[1]
|
||||
mi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -149,7 +286,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{1}
|
||||
return file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *Config) GetResetMin() int64 {
|
||||
@@ -177,7 +314,21 @@ var File_transport_internet_finalmask_noise_config_proto protoreflect.FileDescri
|
||||
|
||||
const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"/transport/internet/finalmask/noise/config.proto\x12'xray.transport.internet.finalmask.noise\"\xda\x01\n" +
|
||||
"/transport/internet/finalmask/noise/config.proto\x12'xray.transport.internet.finalmask.noise\"\x8a\x02\n" +
|
||||
"\aSegment\x12I\n" +
|
||||
"\x04kind\x18\x01 \x01(\x0e25.xray.transport.internet.finalmask.noise.Segment.KindR\x04kind\x12\x14\n" +
|
||||
"\x05bytes\x18\x02 \x01(\fR\x05bytes\x12\x19\n" +
|
||||
"\bmin_size\x18\x03 \x01(\x03R\aminSize\x12\x19\n" +
|
||||
"\bmax_size\x18\x04 \x01(\x03R\amaxSize\"h\n" +
|
||||
"\x04Kind\x12\t\n" +
|
||||
"\x05BYTES\x10\x00\x12\n" +
|
||||
"\n" +
|
||||
"\x06RANDOM\x10\x01\x12\x10\n" +
|
||||
"\fRANDOM_ASCII\x10\x02\x12\x10\n" +
|
||||
"\fRANDOM_DIGIT\x10\x03\x12\r\n" +
|
||||
"\tTIMESTAMP\x10\x04\x12\v\n" +
|
||||
"\aCOUNTER\x10\x05\x12\t\n" +
|
||||
"\x05NONCE\x10\x06\"\xa8\x02\n" +
|
||||
"\x04Item\x12\x19\n" +
|
||||
"\brand_min\x18\x01 \x01(\x03R\arandMin\x12\x19\n" +
|
||||
"\brand_max\x18\x02 \x01(\x03R\arandMax\x12$\n" +
|
||||
@@ -185,7 +336,8 @@ const file_transport_internet_finalmask_noise_config_proto_rawDesc = "" +
|
||||
"\x0erand_range_max\x18\x04 \x01(\x05R\frandRangeMax\x12\x16\n" +
|
||||
"\x06packet\x18\x05 \x01(\fR\x06packet\x12\x1b\n" +
|
||||
"\tdelay_min\x18\x06 \x01(\x03R\bdelayMin\x12\x1b\n" +
|
||||
"\tdelay_max\x18\a \x01(\x03R\bdelayMax\"\x87\x01\n" +
|
||||
"\tdelay_max\x18\a \x01(\x03R\bdelayMax\x12L\n" +
|
||||
"\bsegments\x18\b \x03(\v20.xray.transport.internet.finalmask.noise.SegmentR\bsegments\"\x87\x01\n" +
|
||||
"\x06Config\x12\x1b\n" +
|
||||
"\treset_min\x18\x01 \x01(\x03R\bresetMin\x12\x1b\n" +
|
||||
"\treset_max\x18\x02 \x01(\x03R\bresetMax\x12C\n" +
|
||||
@@ -204,18 +356,23 @@ func file_transport_internet_finalmask_noise_config_proto_rawDescGZIP() []byte {
|
||||
return file_transport_internet_finalmask_noise_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_transport_internet_finalmask_noise_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_transport_internet_finalmask_noise_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_transport_internet_finalmask_noise_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_transport_internet_finalmask_noise_config_proto_goTypes = []any{
|
||||
(*Item)(nil), // 0: xray.transport.internet.finalmask.noise.Item
|
||||
(*Config)(nil), // 1: xray.transport.internet.finalmask.noise.Config
|
||||
(Segment_Kind)(0), // 0: xray.transport.internet.finalmask.noise.Segment.Kind
|
||||
(*Segment)(nil), // 1: xray.transport.internet.finalmask.noise.Segment
|
||||
(*Item)(nil), // 2: xray.transport.internet.finalmask.noise.Item
|
||||
(*Config)(nil), // 3: xray.transport.internet.finalmask.noise.Config
|
||||
}
|
||||
var file_transport_internet_finalmask_noise_config_proto_depIdxs = []int32{
|
||||
0, // 0: xray.transport.internet.finalmask.noise.Config.items:type_name -> xray.transport.internet.finalmask.noise.Item
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
0, // 0: xray.transport.internet.finalmask.noise.Segment.kind:type_name -> xray.transport.internet.finalmask.noise.Segment.Kind
|
||||
1, // 1: xray.transport.internet.finalmask.noise.Item.segments:type_name -> xray.transport.internet.finalmask.noise.Segment
|
||||
2, // 2: xray.transport.internet.finalmask.noise.Config.items:type_name -> xray.transport.internet.finalmask.noise.Item
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_finalmask_noise_config_proto_init() }
|
||||
@@ -228,13 +385,14 @@ func file_transport_internet_finalmask_noise_config_proto_init() {
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_noise_config_proto_rawDesc), len(file_transport_internet_finalmask_noise_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumEnums: 1,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_transport_internet_finalmask_noise_config_proto_goTypes,
|
||||
DependencyIndexes: file_transport_internet_finalmask_noise_config_proto_depIdxs,
|
||||
EnumInfos: file_transport_internet_finalmask_noise_config_proto_enumTypes,
|
||||
MessageInfos: file_transport_internet_finalmask_noise_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_transport_internet_finalmask_noise_config_proto = out.File
|
||||
|
||||
@@ -6,6 +6,22 @@ option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/nois
|
||||
option java_package = "com.xray.transport.internet.finalmask.noise";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Segment {
|
||||
enum Kind {
|
||||
BYTES = 0;
|
||||
RANDOM = 1;
|
||||
RANDOM_ASCII = 2;
|
||||
RANDOM_DIGIT = 3;
|
||||
TIMESTAMP = 4;
|
||||
COUNTER = 5;
|
||||
NONCE = 6;
|
||||
}
|
||||
Kind kind = 1;
|
||||
bytes bytes = 2;
|
||||
int64 min_size = 3;
|
||||
int64 max_size = 4;
|
||||
}
|
||||
|
||||
message Item {
|
||||
int64 rand_min = 1;
|
||||
int64 rand_max = 2;
|
||||
@@ -14,6 +30,7 @@ message Item {
|
||||
bytes packet = 5;
|
||||
int64 delay_min = 6;
|
||||
int64 delay_max = 7;
|
||||
repeated Segment segments = 8;
|
||||
}
|
||||
|
||||
message Config {
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
package noise
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/crypto"
|
||||
)
|
||||
|
||||
const asciiLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
type noiseConn struct {
|
||||
net.PacketConn
|
||||
config *Config
|
||||
m map[string]time.Time
|
||||
mu sync.Mutex
|
||||
config *Config
|
||||
m map[string]time.Time
|
||||
mu sync.Mutex
|
||||
counter atomic.Uint32
|
||||
}
|
||||
|
||||
func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||
@@ -27,6 +34,62 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||
return NewConnClient(c, raw)
|
||||
}
|
||||
|
||||
func (c *noiseConn) buildPacket(item *Item) []byte {
|
||||
if len(item.Segments) == 0 {
|
||||
if item.RandMax > 0 {
|
||||
buf := make([]byte, crypto.RandBetween(item.RandMin, item.RandMax))
|
||||
crypto.RandBytesBetween(buf, byte(item.RandRangeMin), byte(item.RandRangeMax))
|
||||
return buf
|
||||
}
|
||||
return item.Packet
|
||||
}
|
||||
var out []byte
|
||||
for _, seg := range item.Segments {
|
||||
out = append(out, c.buildSegment(seg)...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *noiseConn) buildSegment(seg *Segment) []byte {
|
||||
switch seg.Kind {
|
||||
case Segment_BYTES:
|
||||
return seg.Bytes
|
||||
case Segment_TIMESTAMP:
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(time.Now().Unix()))
|
||||
return b
|
||||
case Segment_COUNTER:
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, c.counter.Add(1))
|
||||
return b
|
||||
case Segment_NONCE:
|
||||
b := make([]byte, 8)
|
||||
common.Must2(rand.Read(b))
|
||||
return b
|
||||
default:
|
||||
size := crypto.RandBetween(seg.MinSize, seg.MaxSize+1)
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
switch seg.Kind {
|
||||
case Segment_RANDOM_ASCII:
|
||||
common.Must2(rand.Read(buf))
|
||||
for i := range buf {
|
||||
buf[i] = asciiLetters[int(buf[i])%len(asciiLetters)]
|
||||
}
|
||||
case Segment_RANDOM_DIGIT:
|
||||
common.Must2(rand.Read(buf))
|
||||
for i := range buf {
|
||||
buf[i] = '0' + buf[i]%10
|
||||
}
|
||||
default:
|
||||
common.Must2(rand.Read(buf))
|
||||
}
|
||||
return buf
|
||||
}
|
||||
}
|
||||
|
||||
func (c *noiseConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
@@ -35,13 +98,7 @@ func (c *noiseConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
|
||||
if t.IsZero() || (c.config.ResetMax > 0 && time.Now().After(t)) {
|
||||
for _, item := range c.config.Items {
|
||||
if item.RandMax > 0 {
|
||||
buf := make([]byte, crypto.RandBetween(item.RandMin, item.RandMax))
|
||||
crypto.RandBytesBetween(buf, byte(item.RandRangeMin), byte(item.RandRangeMax))
|
||||
c.PacketConn.WriteTo(buf, addr)
|
||||
} else {
|
||||
c.PacketConn.WriteTo(item.Packet, addr)
|
||||
}
|
||||
c.PacketConn.WriteTo(c.buildPacket(item), addr)
|
||||
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package noise
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakePacketConn struct {
|
||||
mu sync.Mutex
|
||||
written [][]byte
|
||||
}
|
||||
|
||||
func (c *fakePacketConn) WriteTo(p []byte, _ net.Addr) (int, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.written = append(c.written, bytes.Clone(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *fakePacketConn) packets() [][]byte {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.written
|
||||
}
|
||||
|
||||
func (c *fakePacketConn) ReadFrom(_ []byte) (int, net.Addr, error) { return 0, nil, nil }
|
||||
func (c *fakePacketConn) Close() error { return nil }
|
||||
func (c *fakePacketConn) LocalAddr() net.Addr { return &net.UDPAddr{} }
|
||||
func (c *fakePacketConn) SetDeadline(time.Time) error { return nil }
|
||||
func (c *fakePacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (c *fakePacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
|
||||
func newConn() *noiseConn {
|
||||
return &noiseConn{PacketConn: &fakePacketConn{}, config: &Config{}, m: make(map[string]time.Time)}
|
||||
}
|
||||
|
||||
func TestBuildSegmentBytes(t *testing.T) {
|
||||
c := newConn()
|
||||
got := c.buildSegment(&Segment{Kind: Segment_BYTES, Bytes: []byte{0x0d, 0x0a, 0x0d, 0x0a}})
|
||||
require.Equal(t, []byte{0x0d, 0x0a, 0x0d, 0x0a}, got)
|
||||
}
|
||||
|
||||
func TestBuildSegmentTimestamp(t *testing.T) {
|
||||
c := newConn()
|
||||
before := time.Now().Unix()
|
||||
got := c.buildSegment(&Segment{Kind: Segment_TIMESTAMP})
|
||||
require.Len(t, got, 4)
|
||||
ts := int64(binary.BigEndian.Uint32(got))
|
||||
require.GreaterOrEqual(t, ts, before)
|
||||
require.LessOrEqual(t, ts, time.Now().Unix())
|
||||
}
|
||||
|
||||
func TestBuildSegmentCounter(t *testing.T) {
|
||||
c := newConn()
|
||||
first := binary.BigEndian.Uint32(c.buildSegment(&Segment{Kind: Segment_COUNTER}))
|
||||
second := binary.BigEndian.Uint32(c.buildSegment(&Segment{Kind: Segment_COUNTER}))
|
||||
require.Equal(t, uint32(1), first)
|
||||
require.Equal(t, uint32(2), second)
|
||||
}
|
||||
|
||||
func TestBuildSegmentNonce(t *testing.T) {
|
||||
c := newConn()
|
||||
a := c.buildSegment(&Segment{Kind: Segment_NONCE})
|
||||
b := c.buildSegment(&Segment{Kind: Segment_NONCE})
|
||||
require.Len(t, a, 8)
|
||||
require.Len(t, b, 8)
|
||||
require.NotEqual(t, a, b)
|
||||
}
|
||||
|
||||
func TestBuildSegmentRandomSizes(t *testing.T) {
|
||||
c := newConn()
|
||||
for range 200 {
|
||||
require.Len(t, c.buildSegment(&Segment{Kind: Segment_RANDOM, MinSize: 24, MaxSize: 24}), 24)
|
||||
|
||||
n := len(c.buildSegment(&Segment{Kind: Segment_RANDOM, MinSize: 20, MaxSize: 32}))
|
||||
require.GreaterOrEqual(t, n, 20)
|
||||
require.LessOrEqual(t, n, 32)
|
||||
|
||||
for _, b := range c.buildSegment(&Segment{Kind: Segment_RANDOM_ASCII, MinSize: 40, MaxSize: 40}) {
|
||||
require.True(t, (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z'), "not a letter: %q", b)
|
||||
}
|
||||
for _, b := range c.buildSegment(&Segment{Kind: Segment_RANDOM_DIGIT, MinSize: 40, MaxSize: 40}) {
|
||||
require.True(t, b >= '0' && b <= '9', "not a digit: %q", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPacketComposite(t *testing.T) {
|
||||
c := newConn()
|
||||
item := &Item{Segments: []*Segment{
|
||||
{Kind: Segment_BYTES, Bytes: []byte{0x0d, 0x0a, 0x0d, 0x0a}},
|
||||
{Kind: Segment_TIMESTAMP},
|
||||
{Kind: Segment_RANDOM, MinSize: 24, MaxSize: 24},
|
||||
}}
|
||||
got := c.buildPacket(item)
|
||||
require.Len(t, got, 4+4+24)
|
||||
require.Equal(t, []byte{0x0d, 0x0a, 0x0d, 0x0a}, got[:4])
|
||||
}
|
||||
|
||||
func TestBuildPacketLegacy(t *testing.T) {
|
||||
c := newConn()
|
||||
require.Equal(t, []byte{1, 2, 3}, c.buildPacket(&Item{Packet: []byte{1, 2, 3}}))
|
||||
require.Len(t, c.buildPacket(&Item{RandMin: 16, RandMax: 17}), 16)
|
||||
}
|
||||
|
||||
func TestWriteToSendsNoiseThenPayload(t *testing.T) {
|
||||
raw := &fakePacketConn{}
|
||||
c := &noiseConn{
|
||||
PacketConn: raw,
|
||||
m: make(map[string]time.Time),
|
||||
config: &Config{Items: []*Item{
|
||||
{Segments: []*Segment{{Kind: Segment_BYTES, Bytes: []byte{0x0d, 0x0a, 0x0d, 0x0a}}, {Kind: Segment_RANDOM, MinSize: 8, MaxSize: 8}}},
|
||||
{RandMin: 40, RandMax: 41},
|
||||
}},
|
||||
}
|
||||
addr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 51820}
|
||||
payload := []byte("real-handshake")
|
||||
_, err := c.WriteTo(payload, addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
sent := raw.packets()
|
||||
require.Len(t, sent, 3)
|
||||
require.Len(t, sent[0], 12)
|
||||
require.Equal(t, []byte{0x0d, 0x0a, 0x0d, 0x0a}, sent[0][:4])
|
||||
require.Len(t, sent[1], 40)
|
||||
require.Equal(t, payload, sent[2])
|
||||
|
||||
_, err = c.WriteTo(payload, addr)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, raw.packets(), 4)
|
||||
}
|
||||
Reference in New Issue
Block a user