From c594e7addb20fdf6a1b373a4e90819c3ccb62f33 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Sat, 2 May 2026 18:31:50 +0800 Subject: [PATCH 1/6] fixed lifetime blackhole --- proxy/freedom/freedom.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 8be8dbe053ef..c61a14406cd5 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -266,6 +266,21 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte input := link.Reader output := link.Writer + blackhole := func(blockedDest net.Destination) error { + delay := time.Duration(30+dice.Roll(61)) * time.Second + errors.LogInfo(ctx, "blocked target: ", blockedDest, ", blackholing connection for ", delay) + timer := time.AfterFunc(delay, func() { + common.Interrupt(input) + common.Interrupt(output) + errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", blockedDest) + }) + defer timer.Stop() + defer common.Close(output) + if err := buf.Copy(input, buf.Discard); err != nil { + return nil + } + return nil + } var conn stat.Connection var blockedDest *net.Destination @@ -290,7 +305,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } errors.LogInfo(ctx, "dialing to ", dialDest) } - } else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { + } else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + tcp + domain addrs, err := net.DefaultResolver.LookupIPAddr(ctx, dialDest.Address.Domain()) if err != nil { errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain()) @@ -301,7 +316,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } } } - if dialDest.Network == net.Network_TCP && h.applyFinalRules(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule) == RuleAction_Block { + if h.applyFinalRules(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule) == RuleAction_Block { blockedDest = &dialDest return nil } @@ -318,12 +333,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return errors.New("failed to open connection to ", destination).Base(err) } if blockedDest != nil { - return errors.New("blocked target: ", *blockedDest).AtInfo() + return blackhole(*blockedDest) + } + // SRV/TXT + if remoteDest := net.DestinationFromAddr(conn.RemoteAddr()); h.applyFinalRules(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule) == RuleAction_Block { + conn.Close() + return blackhole(remoteDest) } - // if remoteDest := net.DestinationFromAddr(conn.RemoteAddr()); h.applyFinalRules(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule) == RuleAction_Block { - // conn.Close() - // return errors.New("blocked target: ", remoteDest).AtInfo() - // } if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 { version := byte(h.config.ProxyProtocol) srcAddr := inbound.Source.RawNetAddr() From 3c94cabfa02631ea152620df41f5a9d420c57ca4 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Sat, 2 May 2026 18:46:58 +0800 Subject: [PATCH 2/6] conf --- infra/conf/freedom.go | 8 +++ infra/conf/freedom_test.go | 7 ++- proxy/freedom/config.pb.go | 125 ++++++++++++++++++++++++++++--------- proxy/freedom/config.proto | 6 ++ proxy/freedom/freedom.go | 15 ++++- 5 files changed, 130 insertions(+), 31 deletions(-) diff --git a/infra/conf/freedom.go b/infra/conf/freedom.go index c53be8eb260d..11a670cc0800 100644 --- a/infra/conf/freedom.go +++ b/infra/conf/freedom.go @@ -27,6 +27,7 @@ type FreedomConfig struct { ProxyProtocol uint32 `json:"proxyProtocol"` IPsBlocked *StringList `json:"ipsBlocked"` FinalRules []*FreedomFinalRuleConfig `json:"finalRules"` + BlockDelay *Int32Range `json:"blockDelay"` } type Fragment struct { @@ -189,6 +190,13 @@ func (c *FreedomConfig) Build() (proto.Message, error) { config.FinalRules = append(config.FinalRules, rule) } + if c.BlockDelay != nil { + config.BlockDelay = &freedom.Range{ + Min: uint64(c.BlockDelay.From), + Max: uint64(c.BlockDelay.To), + } + } + return config, nil } diff --git a/infra/conf/freedom_test.go b/infra/conf/freedom_test.go index da60b1d1069f..55f1cdeb7a1a 100644 --- a/infra/conf/freedom_test.go +++ b/infra/conf/freedom_test.go @@ -21,7 +21,8 @@ func TestFreedomConfig(t *testing.T) { Input: `{ "domainStrategy": "AsIs", "redirect": "127.0.0.1:3366", - "userLevel": 1 + "userLevel": 1, + "blockDelay": "30-60" }`, Parser: loadJSON(creator), Output: &freedom.Config{ @@ -37,6 +38,10 @@ func TestFreedomConfig(t *testing.T) { }, }, UserLevel: 1, + BlockDelay: &freedom.Range{ + Min: 30, + Max: 60, + }, }, }, { diff --git a/proxy/freedom/config.pb.go b/proxy/freedom/config.pb.go index e48c7ba756dc..3ef7901a7dbf 100644 --- a/proxy/freedom/config.pb.go +++ b/proxy/freedom/config.pb.go @@ -299,6 +299,58 @@ func (x *Noise) GetApplyTo() string { return "" } +type Range struct { + state protoimpl.MessageState `protogen:"open.v1"` + Min uint64 `protobuf:"varint,1,opt,name=min,proto3" json:"min,omitempty"` + Max uint64 `protobuf:"varint,2,opt,name=max,proto3" json:"max,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Range) Reset() { + *x = Range{} + mi := &file_proxy_freedom_config_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Range) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Range) ProtoMessage() {} + +func (x *Range) ProtoReflect() protoreflect.Message { + mi := &file_proxy_freedom_config_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Range.ProtoReflect.Descriptor instead. +func (*Range) Descriptor() ([]byte, []int) { + return file_proxy_freedom_config_proto_rawDescGZIP(), []int{3} +} + +func (x *Range) GetMin() uint64 { + if x != nil { + return x.Min + } + return 0 +} + +func (x *Range) GetMax() uint64 { + if x != nil { + return x.Max + } + return 0 +} + type FinalRuleConfig struct { state protoimpl.MessageState `protogen:"open.v1"` Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.freedom.RuleAction" json:"action,omitempty"` @@ -311,7 +363,7 @@ type FinalRuleConfig struct { func (x *FinalRuleConfig) Reset() { *x = FinalRuleConfig{} - mi := &file_proxy_freedom_config_proto_msgTypes[3] + mi := &file_proxy_freedom_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -323,7 +375,7 @@ func (x *FinalRuleConfig) String() string { func (*FinalRuleConfig) ProtoMessage() {} func (x *FinalRuleConfig) ProtoReflect() protoreflect.Message { - mi := &file_proxy_freedom_config_proto_msgTypes[3] + mi := &file_proxy_freedom_config_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -336,7 +388,7 @@ func (x *FinalRuleConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalRuleConfig.ProtoReflect.Descriptor instead. func (*FinalRuleConfig) Descriptor() ([]byte, []int) { - return file_proxy_freedom_config_proto_rawDescGZIP(), []int{3} + return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4} } func (x *FinalRuleConfig) GetAction() RuleAction { @@ -376,13 +428,14 @@ type Config struct { ProxyProtocol uint32 `protobuf:"varint,6,opt,name=proxy_protocol,json=proxyProtocol,proto3" json:"proxy_protocol,omitempty"` Noises []*Noise `protobuf:"bytes,7,rep,name=noises,proto3" json:"noises,omitempty"` FinalRules []*FinalRuleConfig `protobuf:"bytes,8,rep,name=final_rules,json=finalRules,proto3" json:"final_rules,omitempty"` + BlockDelay *Range `protobuf:"bytes,9,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Config) Reset() { *x = Config{} - mi := &file_proxy_freedom_config_proto_msgTypes[4] + mi := &file_proxy_freedom_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -394,7 +447,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_proxy_freedom_config_proto_msgTypes[4] + mi := &file_proxy_freedom_config_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -407,7 +460,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4} + return file_proxy_freedom_config_proto_rawDescGZIP(), []int{5} } func (x *Config) GetDomainStrategy() internet.DomainStrategy { @@ -459,6 +512,13 @@ func (x *Config) GetFinalRules() []*FinalRuleConfig { return nil } +func (x *Config) GetBlockDelay() *Range { + if x != nil { + return x.BlockDelay + } + return nil +} + var File_proxy_freedom_config_proto protoreflect.FileDescriptor const file_proxy_freedom_config_proto_rawDesc = "" + @@ -486,12 +546,15 @@ const file_proxy_freedom_config_proto_rawDesc = "" + "\tdelay_min\x18\x03 \x01(\x04R\bdelayMin\x12\x1b\n" + "\tdelay_max\x18\x04 \x01(\x04R\bdelayMax\x12\x16\n" + "\x06packet\x18\x05 \x01(\fR\x06packet\x12\x19\n" + - "\bapply_to\x18\x06 \x01(\tR\aapplyTo\"\xe4\x01\n" + + "\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" + + "\x05Range\x12\x10\n" + + "\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" + + "\x03max\x18\x02 \x01(\x04R\x03max\"\xe4\x01\n" + "\x0fFinalRuleConfig\x126\n" + "\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" + "\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" + "\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" + - "\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\"\xaf\x03\n" + + "\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\"\xeb\x03\n" + "\x06Config\x12P\n" + "\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" + "\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" + @@ -501,7 +564,9 @@ const file_proxy_freedom_config_proto_rawDesc = "" + "\x0eproxy_protocol\x18\x06 \x01(\rR\rproxyProtocol\x121\n" + "\x06noises\x18\a \x03(\v2\x19.xray.proxy.freedom.NoiseR\x06noises\x12D\n" + "\vfinal_rules\x18\b \x03(\v2#.xray.proxy.freedom.FinalRuleConfigR\n" + - "finalRules*\"\n" + + "finalRules\x12:\n" + + "\vblock_delay\x18\t \x01(\v2\x19.xray.proxy.freedom.RangeR\n" + + "blockDelay*\"\n" + "\n" + "RuleAction\x12\t\n" + "\x05Allow\x10\x00\x12\t\n" + @@ -521,36 +586,38 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte { } var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_proxy_freedom_config_proto_goTypes = []any{ (RuleAction)(0), // 0: xray.proxy.freedom.RuleAction (*DestinationOverride)(nil), // 1: xray.proxy.freedom.DestinationOverride (*Fragment)(nil), // 2: xray.proxy.freedom.Fragment (*Noise)(nil), // 3: xray.proxy.freedom.Noise - (*FinalRuleConfig)(nil), // 4: xray.proxy.freedom.FinalRuleConfig - (*Config)(nil), // 5: xray.proxy.freedom.Config - (*protocol.ServerEndpoint)(nil), // 6: xray.common.protocol.ServerEndpoint - (net.Network)(0), // 7: xray.common.net.Network - (*net.PortList)(nil), // 8: xray.common.net.PortList - (*geodata.IPRule)(nil), // 9: xray.common.geodata.IPRule - (internet.DomainStrategy)(0), // 10: xray.transport.internet.DomainStrategy + (*Range)(nil), // 4: xray.proxy.freedom.Range + (*FinalRuleConfig)(nil), // 5: xray.proxy.freedom.FinalRuleConfig + (*Config)(nil), // 6: xray.proxy.freedom.Config + (*protocol.ServerEndpoint)(nil), // 7: xray.common.protocol.ServerEndpoint + (net.Network)(0), // 8: xray.common.net.Network + (*net.PortList)(nil), // 9: xray.common.net.PortList + (*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule + (internet.DomainStrategy)(0), // 11: xray.transport.internet.DomainStrategy } var file_proxy_freedom_config_proto_depIdxs = []int32{ - 6, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint + 7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint 0, // 1: xray.proxy.freedom.FinalRuleConfig.action:type_name -> xray.proxy.freedom.RuleAction - 7, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network - 8, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList - 9, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule - 10, // 5: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy + 8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network + 9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList + 10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule + 11, // 5: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy 1, // 6: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride 2, // 7: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment 3, // 8: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise - 4, // 9: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig - 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 + 5, // 9: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig + 4, // 10: xray.proxy.freedom.Config.block_delay:type_name -> xray.proxy.freedom.Range + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_proxy_freedom_config_proto_init() } @@ -564,7 +631,7 @@ func file_proxy_freedom_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_freedom_config_proto_rawDesc), len(file_proxy_freedom_config_proto_rawDesc)), NumEnums: 1, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/proxy/freedom/config.proto b/proxy/freedom/config.proto index 86242adbd99e..8cc0a000afa8 100644 --- a/proxy/freedom/config.proto +++ b/proxy/freedom/config.proto @@ -36,6 +36,11 @@ message Noise { string apply_to = 6; } +message Range { + uint64 min = 1; + uint64 max = 2; +} + enum RuleAction { Allow = 0; Block = 1; @@ -56,4 +61,5 @@ message Config { uint32 proxy_protocol = 6; repeated Noise noises = 7; repeated FinalRuleConfig final_rules = 8; + Range block_delay = 9; } diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index c61a14406cd5..07727d9d4279 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -223,6 +223,19 @@ func (h *Handler) policy() policy.Session { return p } +func (h *Handler) blockDelay() time.Duration { + minDelay := uint64(30) + maxDelay := uint64(90) + if h.config.BlockDelay != nil { + minDelay = h.config.BlockDelay.Min + maxDelay = h.config.BlockDelay.Max + } + if maxDelay <= minDelay { + return time.Duration(minDelay) * time.Second + } + return time.Duration(minDelay+uint64(dice.Roll(int(maxDelay-minDelay+1)))) * time.Second +} + func isValidAddress(addr *net.IPOrDomain) bool { if addr == nil { return false @@ -267,7 +280,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte input := link.Reader output := link.Writer blackhole := func(blockedDest net.Destination) error { - delay := time.Duration(30+dice.Roll(61)) * time.Second + delay := h.blockDelay() errors.LogInfo(ctx, "blocked target: ", blockedDest, ", blackholing connection for ", delay) timer := time.AfterFunc(delay, func() { common.Interrupt(input) From 8dd1e70b02a0800fd2b812fd8ba639f64a563e9c Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Sat, 2 May 2026 18:59:42 +0800 Subject: [PATCH 3/6] bypass srv/txt --- proxy/freedom/freedom.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 07727d9d4279..410a87c2e8b0 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -349,10 +349,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return blackhole(*blockedDest) } // SRV/TXT - if remoteDest := net.DestinationFromAddr(conn.RemoteAddr()); h.applyFinalRules(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule) == RuleAction_Block { - conn.Close() - return blackhole(remoteDest) - } + // if remoteDest := net.DestinationFromAddr(conn.RemoteAddr()); h.applyFinalRules(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule) == RuleAction_Block { + // conn.Close() + // return blackhole(remoteDest) + // } if h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 { version := byte(h.config.ProxyProtocol) srcAddr := inbound.Source.RawNetAddr() From 4b70ce111a7bdcd9d9c9598dd3b6a785e115bb5a Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Sat, 2 May 2026 20:03:37 +0800 Subject: [PATCH 4/6] per rule --- infra/conf/freedom.go | 24 ++++++++-------- infra/conf/freedom_test.go | 14 +++++----- proxy/freedom/config.pb.go | 38 +++++++++++++------------- proxy/freedom/config.proto | 2 +- proxy/freedom/freedom.go | 56 +++++++++++++++++++++++--------------- 5 files changed, 73 insertions(+), 61 deletions(-) diff --git a/infra/conf/freedom.go b/infra/conf/freedom.go index 11a670cc0800..1d2020e37489 100644 --- a/infra/conf/freedom.go +++ b/infra/conf/freedom.go @@ -27,7 +27,6 @@ type FreedomConfig struct { ProxyProtocol uint32 `json:"proxyProtocol"` IPsBlocked *StringList `json:"ipsBlocked"` FinalRules []*FreedomFinalRuleConfig `json:"finalRules"` - BlockDelay *Int32Range `json:"blockDelay"` } type Fragment struct { @@ -45,10 +44,11 @@ type Noise struct { } type FreedomFinalRuleConfig struct { - Action string `json:"action"` - Network *NetworkList `json:"network"` - Port *PortList `json:"port"` - IP *StringList `json:"ip"` + Action string `json:"action"` + Network *NetworkList `json:"network"` + Port *PortList `json:"port"` + IP *StringList `json:"ip"` + BlockDelay *Int32Range `json:"blockDelay"` } // Build implements Buildable @@ -190,13 +190,6 @@ func (c *FreedomConfig) Build() (proto.Message, error) { config.FinalRules = append(config.FinalRules, rule) } - if c.BlockDelay != nil { - config.BlockDelay = &freedom.Range{ - Min: uint64(c.BlockDelay.From), - Max: uint64(c.BlockDelay.To), - } - } - return config, nil } @@ -284,5 +277,12 @@ func (c *FreedomFinalRuleConfig) Build() (*freedom.FinalRuleConfig, error) { rule.Ip = rules } + if c.BlockDelay != nil { + rule.BlockDelay = &freedom.Range{ + Min: uint64(c.BlockDelay.From), + Max: uint64(c.BlockDelay.To), + } + } + return rule, nil } diff --git a/infra/conf/freedom_test.go b/infra/conf/freedom_test.go index 55f1cdeb7a1a..db7c4b30b96f 100644 --- a/infra/conf/freedom_test.go +++ b/infra/conf/freedom_test.go @@ -21,8 +21,7 @@ func TestFreedomConfig(t *testing.T) { Input: `{ "domainStrategy": "AsIs", "redirect": "127.0.0.1:3366", - "userLevel": 1, - "blockDelay": "30-60" + "userLevel": 1 }`, Parser: loadJSON(creator), Output: &freedom.Config{ @@ -38,10 +37,6 @@ func TestFreedomConfig(t *testing.T) { }, }, UserLevel: 1, - BlockDelay: &freedom.Range{ - Min: 30, - Max: 60, - }, }, }, { @@ -50,7 +45,8 @@ func TestFreedomConfig(t *testing.T) { "action": "block", "network": "tcp,udp", "port": "53,443", - "ip": ["10.0.0.0/8", "2001:db8::/32"] + "ip": ["10.0.0.0/8", "2001:db8::/32"], + "blockDelay": "30-60" }, { "action": "allow", "network": ["udp"] @@ -90,6 +86,10 @@ func TestFreedomConfig(t *testing.T) { }, }, }, + BlockDelay: &freedom.Range{ + Min: 30, + Max: 60, + }, }, { Action: freedom.RuleAction_Allow, diff --git a/proxy/freedom/config.pb.go b/proxy/freedom/config.pb.go index 3ef7901a7dbf..9e58587ac71c 100644 --- a/proxy/freedom/config.pb.go +++ b/proxy/freedom/config.pb.go @@ -357,6 +357,7 @@ type FinalRuleConfig struct { Networks []net.Network `protobuf:"varint,2,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"` PortList *net.PortList `protobuf:"bytes,3,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"` Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"` + BlockDelay *Range `protobuf:"bytes,5,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -419,6 +420,13 @@ func (x *FinalRuleConfig) GetIp() []*geodata.IPRule { return nil } +func (x *FinalRuleConfig) GetBlockDelay() *Range { + if x != nil { + return x.BlockDelay + } + return nil +} + type Config struct { state protoimpl.MessageState `protogen:"open.v1"` DomainStrategy internet.DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"` @@ -428,7 +436,6 @@ type Config struct { ProxyProtocol uint32 `protobuf:"varint,6,opt,name=proxy_protocol,json=proxyProtocol,proto3" json:"proxy_protocol,omitempty"` Noises []*Noise `protobuf:"bytes,7,rep,name=noises,proto3" json:"noises,omitempty"` FinalRules []*FinalRuleConfig `protobuf:"bytes,8,rep,name=final_rules,json=finalRules,proto3" json:"final_rules,omitempty"` - BlockDelay *Range `protobuf:"bytes,9,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -512,13 +519,6 @@ func (x *Config) GetFinalRules() []*FinalRuleConfig { return nil } -func (x *Config) GetBlockDelay() *Range { - if x != nil { - return x.BlockDelay - } - return nil -} - var File_proxy_freedom_config_proto protoreflect.FileDescriptor const file_proxy_freedom_config_proto_rawDesc = "" + @@ -549,12 +549,14 @@ const file_proxy_freedom_config_proto_rawDesc = "" + "\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" + "\x05Range\x12\x10\n" + "\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" + - "\x03max\x18\x02 \x01(\x04R\x03max\"\xe4\x01\n" + + "\x03max\x18\x02 \x01(\x04R\x03max\"\xa0\x02\n" + "\x0fFinalRuleConfig\x126\n" + "\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" + "\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" + "\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" + - "\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\"\xeb\x03\n" + + "\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12:\n" + + "\vblock_delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\n" + + "blockDelay\"\xaf\x03\n" + "\x06Config\x12P\n" + "\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" + "\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" + @@ -564,9 +566,7 @@ const file_proxy_freedom_config_proto_rawDesc = "" + "\x0eproxy_protocol\x18\x06 \x01(\rR\rproxyProtocol\x121\n" + "\x06noises\x18\a \x03(\v2\x19.xray.proxy.freedom.NoiseR\x06noises\x12D\n" + "\vfinal_rules\x18\b \x03(\v2#.xray.proxy.freedom.FinalRuleConfigR\n" + - "finalRules\x12:\n" + - "\vblock_delay\x18\t \x01(\v2\x19.xray.proxy.freedom.RangeR\n" + - "blockDelay*\"\n" + + "finalRules*\"\n" + "\n" + "RuleAction\x12\t\n" + "\x05Allow\x10\x00\x12\t\n" + @@ -607,12 +607,12 @@ var file_proxy_freedom_config_proto_depIdxs = []int32{ 8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network 9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList 10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule - 11, // 5: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy - 1, // 6: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride - 2, // 7: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment - 3, // 8: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise - 5, // 9: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig - 4, // 10: xray.proxy.freedom.Config.block_delay:type_name -> xray.proxy.freedom.Range + 4, // 5: xray.proxy.freedom.FinalRuleConfig.block_delay:type_name -> xray.proxy.freedom.Range + 11, // 6: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy + 1, // 7: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride + 2, // 8: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment + 3, // 9: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise + 5, // 10: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig 11, // [11:11] is the sub-list for method output_type 11, // [11:11] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name diff --git a/proxy/freedom/config.proto b/proxy/freedom/config.proto index 8cc0a000afa8..bd27584c21fe 100644 --- a/proxy/freedom/config.proto +++ b/proxy/freedom/config.proto @@ -51,6 +51,7 @@ message FinalRuleConfig { repeated xray.common.net.Network networks = 2; xray.common.net.PortList port_list = 3; repeated xray.common.geodata.IPRule ip = 4; + Range block_delay = 5; } message Config { @@ -61,5 +62,4 @@ message Config { uint32 proxy_protocol = 6; repeated Noise noises = 7; repeated FinalRuleConfig final_rules = 8; - Range block_delay = 9; } diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 410a87c2e8b0..3573bcb615a7 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -89,10 +89,11 @@ func init() { } type FinalRule struct { - action RuleAction - network [8]bool - port net.MemoryPortList - ip geodata.IPMatcher + action RuleAction + network [8]bool + port net.MemoryPortList + ip geodata.IPMatcher + blockDelay *Range } // Handler handles Freedom connections. @@ -104,7 +105,8 @@ type Handler struct { func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) { rule := &FinalRule{ - action: config.GetAction(), + action: config.GetAction(), + blockDelay: config.GetBlockDelay(), } if len(config.Networks) == 0 { @@ -191,14 +193,21 @@ func (h *Handler) shouldResolveDomainBeforeFinalRules(dialDest net.Destination, return false } -func (h *Handler) applyFinalRules(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) RuleAction { +func (h *Handler) matchFinalRule(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) *FinalRule { for _, rule := range h.finalRules { if rule.Apply(network, address, port) { - return rule.action + return rule } } if defaultRule != nil && defaultRule.Apply(network, address, port) { - return defaultRule.action + return defaultRule + } + return nil +} + +func (h *Handler) applyFinalRules(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) RuleAction { + if rule := h.matchFinalRule(network, address, port, defaultRule); rule != nil { + return rule.action } return RuleAction_Allow } @@ -223,17 +232,18 @@ func (h *Handler) policy() policy.Session { return p } -func (h *Handler) blockDelay() time.Duration { - minDelay := uint64(30) - maxDelay := uint64(90) - if h.config.BlockDelay != nil { - minDelay = h.config.BlockDelay.Min - maxDelay = h.config.BlockDelay.Max +func (h *Handler) blockDelay(rule *FinalRule) time.Duration { + min := uint64(30) + max := uint64(90) + if rule.blockDelay != nil { + min = rule.blockDelay.Min + max = rule.blockDelay.Max } - if maxDelay <= minDelay { - return time.Duration(minDelay) * time.Second + abs := max - min + if max < min { + abs = min - max } - return time.Duration(minDelay+uint64(dice.Roll(int(maxDelay-minDelay+1)))) * time.Second + return time.Duration(min+uint64(dice.Roll(int(abs+1)))) * time.Second } func isValidAddress(addr *net.IPOrDomain) bool { @@ -279,8 +289,8 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte input := link.Reader output := link.Writer - blackhole := func(blockedDest net.Destination) error { - delay := h.blockDelay() + blackhole := func(blockedDest net.Destination, rule *FinalRule) error { + delay := h.blockDelay(rule) errors.LogInfo(ctx, "blocked target: ", blockedDest, ", blackholing connection for ", delay) timer := time.AfterFunc(delay, func() { common.Interrupt(input) @@ -297,6 +307,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte var conn stat.Connection var blockedDest *net.Destination + var blockedRule *FinalRule err := retry.ExponentialBackoff(5, 100).On(func() error { dialDest := destination if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() { @@ -329,8 +340,9 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } } } - if h.applyFinalRules(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule) == RuleAction_Block { + if rule := h.matchFinalRule(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule); rule != nil && rule.action == RuleAction_Block { blockedDest = &dialDest + blockedRule = rule return nil } @@ -346,9 +358,9 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return errors.New("failed to open connection to ", destination).Base(err) } if blockedDest != nil { - return blackhole(*blockedDest) + return blackhole(*blockedDest, blockedRule) } - // SRV/TXT + // TODO: SRV/TXT // if remoteDest := net.DestinationFromAddr(conn.RemoteAddr()); h.applyFinalRules(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule) == RuleAction_Block { // conn.Close() // return blackhole(remoteDest) From 02ce31b9fe111925f3af695d8ce553ecbf159538 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Sat, 2 May 2026 23:58:00 +0800 Subject: [PATCH 5/6] update --- proxy/freedom/freedom.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 3573bcb615a7..31cc012ec3d5 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -289,21 +289,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte input := link.Reader output := link.Writer - blackhole := func(blockedDest net.Destination, rule *FinalRule) error { - delay := h.blockDelay(rule) - errors.LogInfo(ctx, "blocked target: ", blockedDest, ", blackholing connection for ", delay) - timer := time.AfterFunc(delay, func() { - common.Interrupt(input) - common.Interrupt(output) - errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", blockedDest) - }) - defer timer.Stop() - defer common.Close(output) - if err := buf.Copy(input, buf.Discard); err != nil { - return nil - } - return nil - } var conn stat.Connection var blockedDest *net.Destination @@ -358,7 +343,19 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return errors.New("failed to open connection to ", destination).Base(err) } if blockedDest != nil { - return blackhole(*blockedDest, blockedRule) + delay := h.blockDelay(blockedRule) + errors.LogInfo(ctx, "blocked target: ", *blockedDest, ", blackholing connection for ", delay) + timer := time.AfterFunc(delay, func() { + common.Interrupt(input) + common.Interrupt(output) + errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *blockedDest) + }) + defer timer.Stop() + defer common.Close(output) + if err := buf.Copy(input, buf.Discard); err != nil { + return nil + } + return nil } // TODO: SRV/TXT // if remoteDest := net.DestinationFromAddr(conn.RemoteAddr()); h.applyFinalRules(remoteDest.Network, remoteDest.Address, remoteDest.Port, defaultRule) == RuleAction_Block { From 4ff695677a6f707a8f3084865b604de87618bf65 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Sun, 3 May 2026 02:41:35 +0800 Subject: [PATCH 6/6] fix --- proxy/freedom/freedom.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 31cc012ec3d5..4cd48b80dc4e 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -178,7 +178,7 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule { } func (h *Handler) shouldResolveDomainBeforeFinalRules(dialDest net.Destination, defaultRule *FinalRule) bool { - if dialDest.Network != net.Network_TCP || !dialDest.Address.Family().IsDomain() { + if !dialDest.Address.Family().IsDomain() { return false } if len(h.finalRules) > 0 { @@ -314,7 +314,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } errors.LogInfo(ctx, "dialing to ", dialDest) } - } else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + tcp + domain + } else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) { // asis + domain + hasrules addrs, err := net.DefaultResolver.LookupIPAddr(ctx, dialDest.Address.Domain()) if err != nil { errors.LogInfoInner(ctx, err, "failed to get IP address for domain ", dialDest.Address.Domain())