From 63ca2e0c1d31b89f67700c33842431f541fedd25 Mon Sep 17 00:00:00 2001 From: Meow <197331664+Meo597@users.noreply.github.com> Date: Wed, 4 Nov 2026 16:34:58 +0800 Subject: [PATCH] Direct/Freedom outbound's `finalRules`: Add `logLevel`; `blockDelay`->`delay`; Apply default safe policy on MASQUE as well (#7329) https://github.com/XTLS/Xray-core/pull/7281#issuecomment-6482226042 https://github.com/XTLS/Xray-core/issues/7240#issuecomment-6601762396 https://github.com/XTLS/Xray-core/pull/7324#issuecomment-6621358849 https://github.com/XTLS/Xray-core/pull/7329#issuecomment-6622083462 https://github.com/XTLS/Xray-core/pull/7329#issuecomment-6625219097 https://github.com/XTLS/Xray-core/pull/7329#issuecomment-6625305192 --- .github/docker/Dockerfile | 2 +- .github/docker/Dockerfile.usa | 2 +- common/reflect/marshal_test.go | 2 +- infra/conf/freedom.go | 36 +++++++++++++++++-- infra/conf/freedom_test.go | 10 +++--- infra/conf/log.go | 2 +- infra/conf/serial/loader_test.go | 4 +-- infra/conf/xray_test.go | 2 +- proxy/freedom/config.pb.go | 54 +++++++++++++++++----------- proxy/freedom/config.proto | 4 ++- proxy/freedom/freedom.go | 62 +++++++++++++++++++++----------- testing/scenarios/vless_test.go | 16 ++++----- 12 files changed, 132 insertions(+), 64 deletions(-) diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile index efeaca27e..781bc9ce6 100644 --- a/.github/docker/Dockerfile +++ b/.github/docker/Dockerfile @@ -20,7 +20,7 @@ RUN cat </tmp/usr/local/etc/xray/00_log.json { "log": { "error": "/var/log/xray/error.log", - "loglevel": "warning", + "logLevel": "warning", "access": "none", "dnsLog": false } diff --git a/.github/docker/Dockerfile.usa b/.github/docker/Dockerfile.usa index 57786876c..05ed0cbab 100644 --- a/.github/docker/Dockerfile.usa +++ b/.github/docker/Dockerfile.usa @@ -20,7 +20,7 @@ RUN cat </tmp/usr/local/etc/xray/00_log.json { "log": { "error": "/var/log/xray/error.log", - "loglevel": "warning", + "logLevel": "warning", "access": "none", "dnsLog": false } diff --git a/common/reflect/marshal_test.go b/common/reflect/marshal_test.go index 748bc69e0..a343b8ff4 100644 --- a/common/reflect/marshal_test.go +++ b/common/reflect/marshal_test.go @@ -134,7 +134,7 @@ func TestMarshalConfigJson(t *testing.T) { func getConfig() string { return `{ "log": { - "loglevel": "debug" + "logLevel": "debug" }, "stats": {}, "policy": { diff --git a/infra/conf/freedom.go b/infra/conf/freedom.go index da34d8e3f..d825e032b 100644 --- a/infra/conf/freedom.go +++ b/infra/conf/freedom.go @@ -9,6 +9,7 @@ import ( "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/geodata" + "github.com/xtls/xray-core/common/log" xnet "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/proxy/freedom" @@ -48,6 +49,8 @@ type FreedomFinalRuleConfig struct { Network *NetworkList `json:"network"` Port *PortList `json:"port"` IP *StringList `json:"ip"` + Delay *Int32Range `json:"delay"` + LogLevel string `json:"logLevel"` BlockDelay *Int32Range `json:"blockDelay"` } @@ -280,10 +283,37 @@ func (c *FreedomFinalRuleConfig) Build() (*freedom.FinalRuleConfig, error) { } if c.BlockDelay != nil { - rule.BlockDelay = &freedom.Range{ - Min: uint64(c.BlockDelay.From), - Max: uint64(c.BlockDelay.To), + errors.PrintDeprecatedFeatureWarning("finalRules.blockDelay", "finalRules.delay") + c.Delay = c.BlockDelay + } + + if rule.Action != freedom.RuleAction_Block { + if c.Delay != nil || c.LogLevel != "" { + return nil, errors.New("delay and logLevel are only supported for block finalRules") } + return rule, nil + } + + if c.Delay != nil { + rule.Delay = &freedom.Range{ + Min: uint64(c.Delay.From), + Max: uint64(c.Delay.To), + } + } + + switch strings.ToLower(c.LogLevel) { + case "", "info": + rule.LogLevel = log.Severity_Info.Enum() + case "none": + rule.LogLevel = log.Severity_Unknown.Enum() + case "debug": + rule.LogLevel = log.Severity_Debug.Enum() + case "warning": + rule.LogLevel = log.Severity_Warning.Enum() + case "error": + rule.LogLevel = log.Severity_Error.Enum() + default: + return nil, errors.New("unknown finalRules.logLevel: ", c.LogLevel) } return rule, nil diff --git a/infra/conf/freedom_test.go b/infra/conf/freedom_test.go index db7c4b30b..1d50fee7b 100644 --- a/infra/conf/freedom_test.go +++ b/infra/conf/freedom_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/xtls/xray-core/common/geodata" + "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" . "github.com/xtls/xray-core/infra/conf" @@ -46,7 +47,8 @@ func TestFreedomConfig(t *testing.T) { "network": "tcp,udp", "port": "53,443", "ip": ["10.0.0.0/8", "2001:db8::/32"], - "blockDelay": "30-60" + "delay": "30-60", + "logLevel": "debug" }, { "action": "allow", "network": ["udp"] @@ -86,10 +88,8 @@ func TestFreedomConfig(t *testing.T) { }, }, }, - BlockDelay: &freedom.Range{ - Min: 30, - Max: 60, - }, + Delay: &freedom.Range{Min: 30, Max: 60}, + LogLevel: log.Severity_Debug.Enum(), }, { Action: freedom.RuleAction_Allow, diff --git a/infra/conf/log.go b/infra/conf/log.go index fee8f570c..1e9be9dfe 100644 --- a/infra/conf/log.go +++ b/infra/conf/log.go @@ -18,7 +18,7 @@ func DefaultLogConfig() *log.Config { type LogConfig struct { AccessLog string `json:"access"` ErrorLog string `json:"error"` - LogLevel string `json:"loglevel"` + LogLevel string `json:"logLevel"` DNSLog bool `json:"dnsLog"` MaskAddress string `json:"maskAddress"` } diff --git a/infra/conf/serial/loader_test.go b/infra/conf/serial/loader_test.go index e6a745b40..2d70c803c 100644 --- a/infra/conf/serial/loader_test.go +++ b/infra/conf/serial/loader_test.go @@ -18,7 +18,7 @@ func TestLoaderError(t *testing.T) { "log": { // abcd 0, - "loglevel": "info" + "logLevel": "info" } }`, Output: "line 4 char 6", @@ -27,7 +27,7 @@ func TestLoaderError(t *testing.T) { Input: `{ "log": { // abcd - "loglevel": "info", + "logLevel": "info", } }`, Output: "line 5 char 5", diff --git a/infra/conf/xray_test.go b/infra/conf/xray_test.go index 3b7fcddcd..890b80e80 100644 --- a/infra/conf/xray_test.go +++ b/infra/conf/xray_test.go @@ -42,7 +42,7 @@ func TestXrayConfig(t *testing.T) { Input: `{ "log": { "access": "/var/log/xray/access.log", - "loglevel": "error", + "logLevel": "error", "error": "/var/log/xray/error.log" }, "inbounds": [{ diff --git a/proxy/freedom/config.pb.go b/proxy/freedom/config.pb.go index 39aefd028..4bc9c1849 100644 --- a/proxy/freedom/config.pb.go +++ b/proxy/freedom/config.pb.go @@ -8,6 +8,7 @@ package freedom import ( geodata "github.com/xtls/xray-core/common/geodata" + log "github.com/xtls/xray-core/common/log" net "github.com/xtls/xray-core/common/net" protocol "github.com/xtls/xray-core/common/protocol" internet "github.com/xtls/xray-core/transport/internet" @@ -357,7 +358,8 @@ 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"` + Delay *Range `protobuf:"bytes,5,opt,name=delay,proto3" json:"delay,omitempty"` + LogLevel *log.Severity `protobuf:"varint,6,opt,name=log_level,json=logLevel,proto3,enum=xray.common.log.Severity,oneof" json:"log_level,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -420,13 +422,20 @@ func (x *FinalRuleConfig) GetIp() []*geodata.IPRule { return nil } -func (x *FinalRuleConfig) GetBlockDelay() *Range { +func (x *FinalRuleConfig) GetDelay() *Range { if x != nil { - return x.BlockDelay + return x.Delay } return nil } +func (x *FinalRuleConfig) GetLogLevel() log.Severity { + if x != nil && x.LogLevel != nil { + return *x.LogLevel + } + return log.Severity(0) +} + 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"` @@ -523,7 +532,7 @@ var File_proxy_freedom_config_proto protoreflect.FileDescriptor const file_proxy_freedom_config_proto_rawDesc = "" + "\n" + - "\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"S\n" + + "\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\x1a\x14common/log/log.proto\"S\n" + "\x13DestinationOverride\x12<\n" + "\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" + "\bFragment\x12!\n" + @@ -549,14 +558,16 @@ 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\"\xa0\x02\n" + + "\x03max\x18\x02 \x01(\x04R\x03max\"\xe0\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\x12:\n" + - "\vblock_delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\n" + - "blockDelay\"\xaf\x03\n" + + "\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12/\n" + + "\x05delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\x05delay\x12;\n" + + "\tlog_level\x18\x06 \x01(\x0e2\x19.xray.common.log.SeverityH\x00R\blogLevel\x88\x01\x01B\f\n" + + "\n" + + "_log_level\"\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" + @@ -599,7 +610,8 @@ var file_proxy_freedom_config_proto_goTypes = []any{ (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 + (log.Severity)(0), // 11: xray.common.log.Severity + (internet.DomainStrategy)(0), // 12: xray.transport.internet.DomainStrategy } var file_proxy_freedom_config_proto_depIdxs = []int32{ 7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint @@ -607,17 +619,18 @@ 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 - 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 - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 4, // 5: xray.proxy.freedom.FinalRuleConfig.delay:type_name -> xray.proxy.freedom.Range + 11, // 6: xray.proxy.freedom.FinalRuleConfig.log_level:type_name -> xray.common.log.Severity + 12, // 7: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy + 1, // 8: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride + 2, // 9: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment + 3, // 10: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise + 5, // 11: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_proxy_freedom_config_proto_init() } @@ -625,6 +638,7 @@ func file_proxy_freedom_config_proto_init() { if File_proxy_freedom_config_proto != nil { return } + file_proxy_freedom_config_proto_msgTypes[4].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/proxy/freedom/config.proto b/proxy/freedom/config.proto index bd27584c2..9a218877d 100644 --- a/proxy/freedom/config.proto +++ b/proxy/freedom/config.proto @@ -11,6 +11,7 @@ import "transport/internet/config.proto"; import "common/net/port.proto"; import "common/net/network.proto"; import "common/geodata/geodat.proto"; +import "common/log/log.proto"; message DestinationOverride { xray.common.protocol.ServerEndpoint server = 1; @@ -51,7 +52,8 @@ 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; + Range delay = 5; + optional xray.common.log.Severity log_level = 6; } message Config { diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 85ae6eec7..6215647a2 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -15,6 +15,7 @@ import ( "github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/geodata" + "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/retry" @@ -72,23 +73,26 @@ func init() { } defaultBlockPrivateRule = &FinalRule{ - action: RuleAction_Block, - network: allNetworks, - ip: geodata.GetPrivateIPMatcher(), + action: RuleAction_Block, + network: allNetworks, + ip: geodata.GetPrivateIPMatcher(), + logLevel: log.Severity_Warning.Enum(), } defaultBlockAllRule = &FinalRule{ - action: RuleAction_Block, - network: allNetworks, + action: RuleAction_Block, + network: allNetworks, + logLevel: log.Severity_Warning.Enum(), } } type FinalRule struct { - action RuleAction - network [8]bool - port net.MemoryPortList - ip geodata.IPMatcher - blockDelay *Range + action RuleAction + network [8]bool + port net.MemoryPortList + ip geodata.IPMatcher + delay *Range + logLevel *log.Severity } // Handler handles Freedom connections. @@ -102,8 +106,9 @@ type Handler struct { func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) { rule := &FinalRule{ - action: config.GetAction(), - blockDelay: config.GetBlockDelay(), + action: config.GetAction(), + delay: config.GetDelay(), + logLevel: config.LogLevel, } if len(config.Networks) == 0 { @@ -164,7 +169,7 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule { switch inbound.Name { case "vless-reverse": return defaultBlockAllRule - case "vless", "vmess", "trojan", "hysteria", "wireguard": + case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque": return defaultBlockPrivateRule default: if strings.HasPrefix(inbound.Name, "shadowsocks") { @@ -212,12 +217,12 @@ func (h *Handler) policy() policy.Session { return p } -func (h *Handler) blockDelay(rule *FinalRule) time.Duration { +func (r *FinalRule) blockDuration() time.Duration { min := uint64(30) max := uint64(90) - if rule.blockDelay != nil { - min = rule.blockDelay.Min - max = rule.blockDelay.Max + if r.delay != nil { + min = r.delay.Min + max = r.delay.Max } span := max - min if max < min { @@ -226,9 +231,26 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration { return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second } +func (r *FinalRule) log(ctx context.Context, msg ...interface{}) { + level := log.Severity_Info + if r.logLevel != nil { + level = *r.logLevel + } + switch level { + case log.Severity_Debug: + errors.LogDebug(ctx, msg...) + case log.Severity_Info: + errors.LogInfo(ctx, msg...) + case log.Severity_Warning: + errors.LogWarning(ctx, msg...) + case log.Severity_Error: + errors.LogError(ctx, msg...) + } +} + func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error { - delay := h.blockDelay(rule) - errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay) + delay := rule.blockDuration() + rule.log(ctx, "target blocked by a finalrule: ", *dest, ", blackholing connection for ", delay) defer common.Close(output) done := make(chan struct{}) go func() { // a DispatchLink inbound's reader ignores Interrupt, so don't wait for this copy @@ -240,7 +262,7 @@ func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Wr case <-time.After(delay): common.Interrupt(input) common.Interrupt(output) - errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest) + rule.log(ctx, "closed blackholed connection to blocked target: ", *dest) } return nil } diff --git a/testing/scenarios/vless_test.go b/testing/scenarios/vless_test.go index 9733a93b8..a624b0844 100644 --- a/testing/scenarios/vless_test.go +++ b/testing/scenarios/vless_test.go @@ -661,7 +661,7 @@ func TestVlessRealityFingerprints(t *testing.T) { wg.Wait() } -// A blocked connection must be closed after blockDelay even while the client keeps it open and sends nothing. +// A blocked connection must close after finalRules.delay even while the client keeps it open and sends nothing. func TestVlessBlockDelay(t *testing.T) { tcpServer := tcp.Server{ MsgProcessor: xor, @@ -671,7 +671,7 @@ func TestVlessBlockDelay(t *testing.T) { defer tcpServer.Close() for _, delay := range []uint64{0, 1} { - t.Run(fmt.Sprint("blockDelay=", delay), func(t *testing.T) { + t.Run(fmt.Sprint("delay=", delay), func(t *testing.T) { userID := protocol.NewID(uuid.New()) serverPort := tcp.PickPort() serverConfig := &core.Config{ @@ -697,8 +697,8 @@ func TestVlessBlockDelay(t *testing.T) { ProxySettings: serial.ToTypedMessage(&freedom.Config{ FinalRules: []*freedom.FinalRuleConfig{ { - Action: freedom.RuleAction_Block, - BlockDelay: &freedom.Range{Min: delay, Max: delay}, + Action: freedom.RuleAction_Block, + Delay: &freedom.Range{Min: delay, Max: delay}, }, }, }), @@ -726,15 +726,15 @@ func TestVlessBlockDelay(t *testing.T) { Port: dest.Port, }, &encoding.Addons{})) - blockDelay := time.Duration(delay) * time.Second - common.Must(conn.SetReadDeadline(time.Now().Add(blockDelay + 5*time.Second))) + blockDuration := time.Duration(delay) * time.Second + common.Must(conn.SetReadDeadline(time.Now().Add(blockDuration + 5*time.Second))) _, err = io.Copy(io.Discard, conn) elapsed := time.Since(start) if err, ok := err.(net.Error); ok && err.Timeout() { t.Fatalf("connection is still open after %v", elapsed) } - if elapsed < blockDelay { - t.Fatalf("connection is closed after %v, before blockDelay", elapsed) + if elapsed < blockDuration { + t.Fatalf("connection is closed after %v, before finalRules.delay", elapsed) } }) }