mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
Direct/Freedom outbound: Add finalRules (matches network, port and ip, then action) with default safe policies (#6027)
Document: https://xtls.github.io/config/outbounds/freedom.html#finalruleobject https://github.com/XTLS/Xray-core/pull/6027#issuecomment-4335790980 https://github.com/XTLS/Xray-core/pull/6027#issuecomment-4336309055 https://github.com/XTLS/Xray-core/pull/6027#issuecomment-4338269638 Closes https://github.com/XTLS/Xray-core/issues/6018#issuecomment-4329273637 --------- Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
This commit is contained in:
@@ -1 +0,0 @@
|
||||
package freedom
|
||||
@@ -8,6 +8,7 @@ package freedom
|
||||
|
||||
import (
|
||||
geodata "github.com/xtls/xray-core/common/geodata"
|
||||
net "github.com/xtls/xray-core/common/net"
|
||||
protocol "github.com/xtls/xray-core/common/protocol"
|
||||
internet "github.com/xtls/xray-core/transport/internet"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
@@ -24,6 +25,52 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type RuleAction int32
|
||||
|
||||
const (
|
||||
RuleAction_Allow RuleAction = 0
|
||||
RuleAction_Block RuleAction = 1
|
||||
)
|
||||
|
||||
// Enum value maps for RuleAction.
|
||||
var (
|
||||
RuleAction_name = map[int32]string{
|
||||
0: "Allow",
|
||||
1: "Block",
|
||||
}
|
||||
RuleAction_value = map[string]int32{
|
||||
"Allow": 0,
|
||||
"Block": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x RuleAction) Enum() *RuleAction {
|
||||
p := new(RuleAction)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x RuleAction) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (RuleAction) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_proxy_freedom_config_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (RuleAction) Type() protoreflect.EnumType {
|
||||
return &file_proxy_freedom_config_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x RuleAction) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RuleAction.Descriptor instead.
|
||||
func (RuleAction) EnumDescriptor() ([]byte, []int) {
|
||||
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type DestinationOverride struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
|
||||
@@ -252,27 +299,30 @@ func (x *Noise) GetApplyTo() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type IPRules struct {
|
||||
type FinalRuleConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Rules []*geodata.IPRule `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty"`
|
||||
Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.freedom.RuleAction" json:"action,omitempty"`
|
||||
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"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *IPRules) Reset() {
|
||||
*x = IPRules{}
|
||||
func (x *FinalRuleConfig) Reset() {
|
||||
*x = FinalRuleConfig{}
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *IPRules) String() string {
|
||||
func (x *FinalRuleConfig) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IPRules) ProtoMessage() {}
|
||||
func (*FinalRuleConfig) ProtoMessage() {}
|
||||
|
||||
func (x *IPRules) ProtoReflect() protoreflect.Message {
|
||||
func (x *FinalRuleConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -284,14 +334,35 @@ func (x *IPRules) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IPRules.ProtoReflect.Descriptor instead.
|
||||
func (*IPRules) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use FinalRuleConfig.ProtoReflect.Descriptor instead.
|
||||
func (*FinalRuleConfig) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *IPRules) GetRules() []*geodata.IPRule {
|
||||
func (x *FinalRuleConfig) GetAction() RuleAction {
|
||||
if x != nil {
|
||||
return x.Rules
|
||||
return x.Action
|
||||
}
|
||||
return RuleAction_Allow
|
||||
}
|
||||
|
||||
func (x *FinalRuleConfig) GetNetworks() []net.Network {
|
||||
if x != nil {
|
||||
return x.Networks
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FinalRuleConfig) GetPortList() *net.PortList {
|
||||
if x != nil {
|
||||
return x.PortList
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FinalRuleConfig) GetIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -304,7 +375,7 @@ type Config struct {
|
||||
Fragment *Fragment `protobuf:"bytes,5,opt,name=fragment,proto3" json:"fragment,omitempty"`
|
||||
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"`
|
||||
IpsBlocked *IPRules `protobuf:"bytes,8,opt,name=ips_blocked,json=ipsBlocked,proto3,oneof" json:"ips_blocked,omitempty"`
|
||||
FinalRules []*FinalRuleConfig `protobuf:"bytes,8,rep,name=final_rules,json=finalRules,proto3" json:"final_rules,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -381,9 +452,9 @@ func (x *Config) GetNoises() []*Noise {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetIpsBlocked() *IPRules {
|
||||
func (x *Config) GetFinalRules() []*FinalRuleConfig {
|
||||
if x != nil {
|
||||
return x.IpsBlocked
|
||||
return x.FinalRules
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -392,7 +463,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\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\"S\n" +
|
||||
"\x13DestinationOverride\x12<\n" +
|
||||
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" +
|
||||
"\bFragment\x12!\n" +
|
||||
@@ -415,9 +486,12 @@ 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\"<\n" +
|
||||
"\aIPRules\x121\n" +
|
||||
"\x05rules\x18\x01 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x05rules\"\xbc\x03\n" +
|
||||
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"\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" +
|
||||
"\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" +
|
||||
@@ -425,10 +499,13 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
|
||||
"user_level\x18\x04 \x01(\rR\tuserLevel\x128\n" +
|
||||
"\bfragment\x18\x05 \x01(\v2\x1c.xray.proxy.freedom.FragmentR\bfragment\x12%\n" +
|
||||
"\x0eproxy_protocol\x18\x06 \x01(\rR\rproxyProtocol\x121\n" +
|
||||
"\x06noises\x18\a \x03(\v2\x19.xray.proxy.freedom.NoiseR\x06noises\x12A\n" +
|
||||
"\vips_blocked\x18\b \x01(\v2\x1b.xray.proxy.freedom.IPRulesH\x00R\n" +
|
||||
"ipsBlocked\x88\x01\x01B\x0e\n" +
|
||||
"\f_ips_blockedBX\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" +
|
||||
"\n" +
|
||||
"RuleAction\x12\t\n" +
|
||||
"\x05Allow\x10\x00\x12\t\n" +
|
||||
"\x05Block\x10\x01BX\n" +
|
||||
"\x16com.xray.proxy.freedomP\x01Z'github.com/xtls/xray-core/proxy/freedom\xaa\x02\x12Xray.Proxy.Freedomb\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -443,30 +520,37 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte {
|
||||
return file_proxy_freedom_config_proto_rawDescData
|
||||
}
|
||||
|
||||
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_goTypes = []any{
|
||||
(*DestinationOverride)(nil), // 0: xray.proxy.freedom.DestinationOverride
|
||||
(*Fragment)(nil), // 1: xray.proxy.freedom.Fragment
|
||||
(*Noise)(nil), // 2: xray.proxy.freedom.Noise
|
||||
(*IPRules)(nil), // 3: xray.proxy.freedom.IPRules
|
||||
(*Config)(nil), // 4: xray.proxy.freedom.Config
|
||||
(*protocol.ServerEndpoint)(nil), // 5: xray.common.protocol.ServerEndpoint
|
||||
(*geodata.IPRule)(nil), // 6: xray.common.geodata.IPRule
|
||||
(internet.DomainStrategy)(0), // 7: xray.transport.internet.DomainStrategy
|
||||
(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
|
||||
}
|
||||
var file_proxy_freedom_config_proto_depIdxs = []int32{
|
||||
5, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||
6, // 1: xray.proxy.freedom.IPRules.rules:type_name -> xray.common.geodata.IPRule
|
||||
7, // 2: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
0, // 3: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
|
||||
1, // 4: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
|
||||
2, // 5: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
|
||||
3, // 6: xray.proxy.freedom.Config.ips_blocked:type_name -> xray.proxy.freedom.IPRules
|
||||
7, // [7:7] is the sub-list for method output_type
|
||||
7, // [7:7] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
6, // 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
|
||||
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
|
||||
}
|
||||
|
||||
func init() { file_proxy_freedom_config_proto_init() }
|
||||
@@ -474,19 +558,19 @@ 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{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_freedom_config_proto_rawDesc), len(file_proxy_freedom_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumEnums: 1,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proxy_freedom_config_proto_goTypes,
|
||||
DependencyIndexes: file_proxy_freedom_config_proto_depIdxs,
|
||||
EnumInfos: file_proxy_freedom_config_proto_enumTypes,
|
||||
MessageInfos: file_proxy_freedom_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proxy_freedom_config_proto = out.File
|
||||
|
||||
@@ -8,6 +8,8 @@ option java_multiple_files = true;
|
||||
|
||||
import "common/protocol/server_spec.proto";
|
||||
import "transport/internet/config.proto";
|
||||
import "common/net/port.proto";
|
||||
import "common/net/network.proto";
|
||||
import "common/geodata/geodat.proto";
|
||||
|
||||
message DestinationOverride {
|
||||
@@ -24,6 +26,7 @@ message Fragment {
|
||||
uint64 max_split_min = 7;
|
||||
uint64 max_split_max = 8;
|
||||
}
|
||||
|
||||
message Noise {
|
||||
uint64 length_min = 1;
|
||||
uint64 length_max = 2;
|
||||
@@ -33,8 +36,16 @@ message Noise {
|
||||
string apply_to = 6;
|
||||
}
|
||||
|
||||
message IPRules {
|
||||
repeated xray.common.geodata.IPRule rules = 1;
|
||||
enum RuleAction {
|
||||
Allow = 0;
|
||||
Block = 1;
|
||||
}
|
||||
|
||||
message FinalRuleConfig {
|
||||
RuleAction action = 1;
|
||||
repeated xray.common.net.Network networks = 2;
|
||||
xray.common.net.PortList port_list = 3;
|
||||
repeated xray.common.geodata.IPRule ip = 4;
|
||||
}
|
||||
|
||||
message Config {
|
||||
@@ -44,5 +55,5 @@ message Config {
|
||||
Fragment fragment = 5;
|
||||
uint32 proxy_protocol = 6;
|
||||
repeated Noise noises = 7;
|
||||
optional IPRules ips_blocked = 8;
|
||||
repeated FinalRuleConfig final_rules = 8;
|
||||
}
|
||||
|
||||
@@ -31,32 +31,9 @@ import (
|
||||
)
|
||||
|
||||
var useSplice bool
|
||||
|
||||
var defaultPrivateBlockIP = []string{
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.0.2.0/24",
|
||||
"192.88.99.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"224.0.0.0/3",
|
||||
"::/127",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
"ff00::/8",
|
||||
}
|
||||
|
||||
var defaultPrivateBlockIPMatcher = func() geodata.IPMatcher {
|
||||
rules := common.Must2(geodata.ParseIPRules(defaultPrivateBlockIP))
|
||||
return common.Must2(geodata.IPReg.BuildIPMatcher(rules))
|
||||
}()
|
||||
var allNetworks [8]bool
|
||||
var defaultBlockPrivateRule *FinalRule
|
||||
var defaultBlockAllRule *FinalRule
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
@@ -68,31 +45,175 @@ func init() {
|
||||
}
|
||||
return h, nil
|
||||
}))
|
||||
|
||||
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
||||
value := platform.NewEnvFlag(platform.UseFreedomSplice).GetValue(func() string { return defaultFlagValue })
|
||||
switch value {
|
||||
case defaultFlagValue, "auto", "enable":
|
||||
useSplice = true
|
||||
}
|
||||
|
||||
for i := range allNetworks {
|
||||
allNetworks[i] = true
|
||||
}
|
||||
|
||||
defaultBlockPrivateRule = &FinalRule{
|
||||
action: RuleAction_Block,
|
||||
network: allNetworks,
|
||||
ip: common.Must2(geodata.IPReg.BuildIPMatcher(common.Must2(geodata.ParseIPRules([]string{
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.0.2.0/24",
|
||||
"192.88.99.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"224.0.0.0/3",
|
||||
"::/127",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
"ff00::/8",
|
||||
})))),
|
||||
}
|
||||
|
||||
defaultBlockAllRule = &FinalRule{
|
||||
action: RuleAction_Block,
|
||||
network: allNetworks,
|
||||
}
|
||||
}
|
||||
|
||||
type FinalRule struct {
|
||||
action RuleAction
|
||||
network [8]bool
|
||||
port net.MemoryPortList
|
||||
ip geodata.IPMatcher
|
||||
}
|
||||
|
||||
// Handler handles Freedom connections.
|
||||
type Handler struct {
|
||||
policyManager policy.Manager
|
||||
config *Config
|
||||
blockedIPMatcher geodata.IPMatcher
|
||||
policyManager policy.Manager
|
||||
config *Config
|
||||
finalRules []*FinalRule
|
||||
}
|
||||
|
||||
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
|
||||
rule := &FinalRule{
|
||||
action: config.GetAction(),
|
||||
}
|
||||
|
||||
if len(config.Networks) == 0 {
|
||||
rule.network = allNetworks
|
||||
} else {
|
||||
for _, network := range config.Networks {
|
||||
rule.network[int(network)] = true
|
||||
}
|
||||
}
|
||||
|
||||
if config.PortList != nil {
|
||||
rule.port = net.PortListFromProto(config.PortList)
|
||||
}
|
||||
|
||||
if len(config.Ip) > 0 {
|
||||
matcher, err := geodata.IPReg.BuildIPMatcher(config.Ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.ip = matcher
|
||||
}
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func (r *FinalRule) matchNetwork(network net.Network) bool {
|
||||
return r.network[int(network)]
|
||||
}
|
||||
|
||||
func (r *FinalRule) matchPort(port net.Port) bool {
|
||||
if len(r.port) == 0 {
|
||||
return true
|
||||
}
|
||||
return r.port.Contains(port)
|
||||
}
|
||||
|
||||
func (r *FinalRule) matchIP(addr net.Address) bool {
|
||||
if r.ip == nil {
|
||||
return true
|
||||
}
|
||||
return addr != nil && addr.Family().IsIP() && r.ip.Match(addr.IP())
|
||||
}
|
||||
|
||||
func (r *FinalRule) Apply(network net.Network, address net.Address, port net.Port) bool {
|
||||
if !r.matchNetwork(network) {
|
||||
return false
|
||||
}
|
||||
if !r.matchPort(port) {
|
||||
return false
|
||||
}
|
||||
return r.matchIP(address)
|
||||
}
|
||||
|
||||
func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
|
||||
if inbound == nil {
|
||||
return nil
|
||||
}
|
||||
switch inbound.Name {
|
||||
case "vless-reverse":
|
||||
return defaultBlockAllRule
|
||||
case "vless", "vmess", "trojan", "hysteria", "wireguard":
|
||||
return defaultBlockPrivateRule
|
||||
default:
|
||||
if strings.HasPrefix(inbound.Name, "shadowsocks") {
|
||||
return defaultBlockPrivateRule
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) shouldResolveDomainBeforeFinalRules(dialDest net.Destination, defaultRule *FinalRule) bool {
|
||||
if dialDest.Network != net.Network_TCP || !dialDest.Address.Family().IsDomain() {
|
||||
return false
|
||||
}
|
||||
if len(h.finalRules) > 0 {
|
||||
rule := h.finalRules[0]
|
||||
if rule.action == RuleAction_Allow && rule.network[dialDest.Network] && len(rule.port) == 0 && rule.ip == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if defaultRule != nil || len(h.finalRules) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *Handler) applyFinalRules(network net.Network, address net.Address, port net.Port, defaultRule *FinalRule) RuleAction {
|
||||
for _, rule := range h.finalRules {
|
||||
if rule.Apply(network, address, port) {
|
||||
return rule.action
|
||||
}
|
||||
}
|
||||
if defaultRule != nil && defaultRule.Apply(network, address, port) {
|
||||
return defaultRule.action
|
||||
}
|
||||
return RuleAction_Allow
|
||||
}
|
||||
|
||||
// Init initializes the Handler with necessary parameters.
|
||||
func (h *Handler) Init(config *Config, pm policy.Manager) error {
|
||||
h.config = config
|
||||
h.policyManager = pm
|
||||
if config.IpsBlocked != nil && len(config.IpsBlocked.Rules) > 0 {
|
||||
m, err := geodata.IPReg.BuildIPMatcher(config.IpsBlocked.Rules)
|
||||
h.finalRules = make([]*FinalRule, 0, len(config.FinalRules))
|
||||
for _, rc := range config.FinalRules {
|
||||
rule, err := buildFinalRule(rc)
|
||||
if err != nil {
|
||||
return errors.New("failed to build blocked ip matcher").Base(err)
|
||||
return errors.New("failed to build final rule").Base(err)
|
||||
}
|
||||
h.blockedIPMatcher = m
|
||||
h.finalRules = append(h.finalRules, rule)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -111,32 +232,6 @@ func isValidAddress(addr *net.IPOrDomain) bool {
|
||||
return a != net.AnyIP && a != net.AnyIPv6
|
||||
}
|
||||
|
||||
func (h *Handler) getBlockedIPMatcher(ctx context.Context, inbound *session.Inbound) geodata.IPMatcher {
|
||||
if h.blockedIPMatcher != nil {
|
||||
return h.blockedIPMatcher
|
||||
}
|
||||
if h.config.IpsBlocked != nil && len(h.config.IpsBlocked.Rules) == 0 { // "ipsBlocked": []
|
||||
return nil
|
||||
}
|
||||
if inbound == nil {
|
||||
return nil
|
||||
}
|
||||
switch inbound.Name {
|
||||
case "vmess", "trojan", "hysteria", "wireguard":
|
||||
errors.LogInfo(ctx, "applying default private IP blocking policy for inbound ", inbound.Name)
|
||||
return defaultPrivateBlockIPMatcher
|
||||
}
|
||||
if strings.HasPrefix(inbound.Name, "vless") || strings.HasPrefix(inbound.Name, "shadowsocks") {
|
||||
errors.LogInfo(ctx, "applying default private IP blocking policy for inbound ", inbound.Name)
|
||||
return defaultPrivateBlockIPMatcher
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isBlockedAddress(matcher geodata.IPMatcher, addr net.Address) bool {
|
||||
return matcher != nil && addr != nil && addr.Family().IsIP() && matcher.Match(addr.IP())
|
||||
}
|
||||
|
||||
// Process implements proxy.Outbound.
|
||||
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
@@ -147,7 +242,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
ob.Name = "freedom"
|
||||
ob.CanSpliceCopy = 1
|
||||
inbound := session.InboundFromContext(ctx)
|
||||
blockedIPMatcher := h.getBlockedIPMatcher(ctx, inbound)
|
||||
defaultRule := getDefaultFinalRule(inbound)
|
||||
|
||||
destination := ob.Target
|
||||
origTargetAddr := ob.OriginalTarget.Address
|
||||
@@ -173,6 +268,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
output := link.Writer
|
||||
|
||||
var conn stat.Connection
|
||||
var blockedDest *net.Destination
|
||||
err := retry.ExponentialBackoff(5, 100).On(func() error {
|
||||
dialDest := destination
|
||||
if h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() {
|
||||
@@ -194,6 +290,20 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
}
|
||||
errors.LogInfo(ctx, "dialing to ", dialDest)
|
||||
}
|
||||
} else if h.shouldResolveDomainBeforeFinalRules(dialDest, defaultRule) {
|
||||
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())
|
||||
} else if len(addrs) > 0 {
|
||||
if addr := net.IPAddress(addrs[dice.Roll(len(addrs))].IP); addr != nil {
|
||||
dialDest.Address = addr
|
||||
errors.LogInfo(ctx, "dialing to ", dialDest)
|
||||
}
|
||||
}
|
||||
}
|
||||
if dialDest.Network == net.Network_TCP && h.applyFinalRules(dialDest.Network, dialDest.Address, dialDest.Port, defaultRule) == RuleAction_Block {
|
||||
blockedDest = &dialDest
|
||||
return nil
|
||||
}
|
||||
|
||||
rawConn, err := dialer.Dial(ctx, dialDest)
|
||||
@@ -207,10 +317,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
if err != nil {
|
||||
return errors.New("failed to open connection to ", destination).Base(err)
|
||||
}
|
||||
if remoteAddr := net.DestinationFromAddr(conn.RemoteAddr()).Address; isBlockedAddress(blockedIPMatcher, remoteAddr) {
|
||||
conn.Close()
|
||||
return errors.New("blocked target IP: ", remoteAddr).AtInfo()
|
||||
if blockedDest != nil {
|
||||
return errors.New("blocked target: ", *blockedDest).AtInfo()
|
||||
}
|
||||
// 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()
|
||||
@@ -255,7 +368,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
writer = buf.NewWriter(conn)
|
||||
}
|
||||
} else {
|
||||
writer = NewPacketWriter(conn, h, UDPOverride, destination, blockedIPMatcher)
|
||||
writer = NewPacketWriter(conn, h, defaultRule, UDPOverride, destination)
|
||||
if h.config.Noises != nil {
|
||||
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
||||
writer = &NoisePacketWriter{
|
||||
@@ -290,7 +403,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
if destination.Network == net.Network_TCP {
|
||||
reader = buf.NewReader(conn)
|
||||
} else {
|
||||
reader = NewPacketReader(conn, UDPOverride, destination, blockedIPMatcher)
|
||||
reader = NewPacketReader(conn, h, defaultRule, UDPOverride, destination)
|
||||
}
|
||||
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
|
||||
return errors.New("failed to process response").Base(err)
|
||||
@@ -309,7 +422,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination, blockedIPMatcher geodata.IPMatcher) buf.Reader {
|
||||
func NewPacketReader(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
|
||||
iConn := conn
|
||||
statConn, ok := iConn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
@@ -328,7 +441,8 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.De
|
||||
return &PacketReader{
|
||||
PacketConnWrapper: c,
|
||||
Counter: counter,
|
||||
BlockedIPMatcher: blockedIPMatcher,
|
||||
Handler: h,
|
||||
DefaultRule: defaultRule,
|
||||
IsOverridden: isOverridden,
|
||||
InitUnchangedAddr: DialDest.Address,
|
||||
InitChangedAddr: net.DestinationFromAddr(conn.RemoteAddr()).Address,
|
||||
@@ -340,7 +454,8 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.De
|
||||
type PacketReader struct {
|
||||
*internet.PacketConnWrapper
|
||||
stats.Counter
|
||||
BlockedIPMatcher geodata.IPMatcher
|
||||
Handler *Handler
|
||||
DefaultRule *FinalRule
|
||||
IsOverridden bool
|
||||
InitUnchangedAddr net.Address
|
||||
InitChangedAddr net.Address
|
||||
@@ -357,7 +472,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
}
|
||||
udpAddr := d.(*net.UDPAddr)
|
||||
sourceAddr := net.IPAddress(udpAddr.IP)
|
||||
if isBlockedAddress(r.BlockedIPMatcher, sourceAddr) {
|
||||
if r.Handler.applyFinalRules(net.Network_UDP, sourceAddr, net.Port(udpAddr.Port), r.DefaultRule) == RuleAction_Block {
|
||||
continue
|
||||
}
|
||||
b.Resize(0, int32(n))
|
||||
@@ -382,7 +497,7 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
}
|
||||
|
||||
// DialDest means the dial target used in the dialer when creating conn
|
||||
func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, DialDest net.Destination, blockedIPMatcher geodata.IPMatcher) buf.Writer {
|
||||
func NewPacketWriter(conn net.Conn, h *Handler, defaultRule *FinalRule, UDPOverride net.Destination, DialDest net.Destination) buf.Writer {
|
||||
iConn := conn
|
||||
statConn, ok := iConn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
@@ -403,7 +518,7 @@ func NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, Dia
|
||||
PacketConnWrapper: c,
|
||||
Counter: counter,
|
||||
Handler: h,
|
||||
BlockedIPMatcher: blockedIPMatcher,
|
||||
DefaultRule: defaultRule,
|
||||
UDPOverride: UDPOverride,
|
||||
ResolvedUDPAddr: resolvedUDPAddr,
|
||||
LocalAddr: net.DestinationFromAddr(conn.LocalAddr()).Address,
|
||||
@@ -417,8 +532,8 @@ type PacketWriter struct {
|
||||
*internet.PacketConnWrapper
|
||||
stats.Counter
|
||||
*Handler
|
||||
BlockedIPMatcher geodata.IPMatcher
|
||||
UDPOverride net.Destination
|
||||
DefaultRule *FinalRule
|
||||
UDPOverride net.Destination
|
||||
|
||||
// Dest of udp packets might be a domain, we will resolve them to IP
|
||||
// But resolver will return a random one if the domain has many IPs
|
||||
@@ -476,7 +591,7 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if isBlockedAddress(w.BlockedIPMatcher, b.UDP.Address) {
|
||||
if w.applyFinalRules(net.Network_UDP, b.UDP.Address, b.UDP.Port, w.DefaultRule) == RuleAction_Block {
|
||||
b.Release()
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user