diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index 4800f70b..f6cfd76e 100644 --- a/app/dispatcher/default.go +++ b/app/dispatcher/default.go @@ -237,6 +237,9 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu if request.ExcludeForDomain != nil && request.ExcludeForDomain.MatchAny(strings.ToLower(domain)) { return false } + if request.ExcludeForIP != nil && destination.Address.Family().IsIP() && request.ExcludeForIP.Match(destination.Address.IP()) { + return false + } protocolString := result.Protocol() if resComp, ok := result.(SnifferResultComposite); ok { protocolString = resComp.ProtocolForDomainResult() diff --git a/app/proxyman/config.go b/app/proxyman/config.go index b5396cd1..5d2b2363 100644 --- a/app/proxyman/config.go +++ b/app/proxyman/config.go @@ -23,5 +23,12 @@ func BuildSniffingRequest(config *SniffingConfig) (session.SniffingRequest, erro } request.ExcludeForDomain = excludeForDomain } + if len(config.IpsExcluded) > 0 { + excludeForIP, err := geodata.IPReg.BuildIPMatcher(config.IpsExcluded) + if err != nil { + return session.SniffingRequest{}, err + } + request.ExcludeForIP = excludeForIP + } return request, nil } diff --git a/app/proxyman/config.pb.go b/app/proxyman/config.pb.go index 9bde2724..8b9dfd27 100644 --- a/app/proxyman/config.pb.go +++ b/app/proxyman/config.pb.go @@ -69,6 +69,7 @@ type SniffingConfig struct { // Supported values are "http", "tls", "fakedns". DestinationOverride []string `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"` DomainsExcluded []*geodata.DomainRule `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,omitempty"` + IpsExcluded []*geodata.IPRule `protobuf:"bytes,6,rep,name=ips_excluded,json=ipsExcluded,proto3" json:"ips_excluded,omitempty"` // Whether should only try to sniff metadata without waiting for client input. // Can be used to support SMTP like protocol where server send the first // message. @@ -129,6 +130,13 @@ func (x *SniffingConfig) GetDomainsExcluded() []*geodata.DomainRule { return nil } +func (x *SniffingConfig) GetIpsExcluded() []*geodata.IPRule { + if x != nil { + return x.IpsExcluded + } + return nil +} + func (x *SniffingConfig) GetMetadataOnly() bool { if x != nil { return x.MetadataOnly @@ -479,11 +487,12 @@ var File_app_proxyman_config_proto protoreflect.FileDescriptor const file_app_proxyman_config_proto_rawDesc = "" + "\n" + "\x19app/proxyman/config.proto\x12\x11xray.app.proxyman\x1a\x18common/net/address.proto\x1a\x15common/net/port.proto\x1a\x1ftransport/internet/config.proto\x1a!common/serial/typed_message.proto\x1a\x1bcommon/geodata/geodat.proto\"\x0f\n" + - "\rInboundConfig\"\xed\x01\n" + + "\rInboundConfig\"\xad\x02\n" + "\x0eSniffingConfig\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x121\n" + "\x14destination_override\x18\x02 \x03(\tR\x13destinationOverride\x12J\n" + - "\x10domains_excluded\x18\x03 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x0fdomainsExcluded\x12#\n" + + "\x10domains_excluded\x18\x03 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x0fdomainsExcluded\x12>\n" + + "\fips_excluded\x18\x06 \x03(\v2\x1b.xray.common.geodata.IPRuleR\vipsExcluded\x12#\n" + "\rmetadata_only\x18\x04 \x01(\bR\fmetadataOnly\x12\x1d\n" + "\n" + "route_only\x18\x05 \x01(\bR\trouteOnly\"\xe5\x02\n" + @@ -534,31 +543,33 @@ var file_app_proxyman_config_proto_goTypes = []any{ (*SenderConfig)(nil), // 5: xray.app.proxyman.SenderConfig (*MultiplexingConfig)(nil), // 6: xray.app.proxyman.MultiplexingConfig (*geodata.DomainRule)(nil), // 7: xray.common.geodata.DomainRule - (*net.PortList)(nil), // 8: xray.common.net.PortList - (*net.IPOrDomain)(nil), // 9: xray.common.net.IPOrDomain - (*internet.StreamConfig)(nil), // 10: xray.transport.internet.StreamConfig - (*serial.TypedMessage)(nil), // 11: xray.common.serial.TypedMessage - (*internet.ProxyConfig)(nil), // 12: xray.transport.internet.ProxyConfig - (internet.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy + (*geodata.IPRule)(nil), // 8: xray.common.geodata.IPRule + (*net.PortList)(nil), // 9: xray.common.net.PortList + (*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain + (*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig + (*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage + (*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig + (internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy } var file_app_proxyman_config_proto_depIdxs = []int32{ 7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule - 8, // 1: xray.app.proxyman.ReceiverConfig.port_list:type_name -> xray.common.net.PortList - 9, // 2: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain - 10, // 3: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig - 1, // 4: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig - 11, // 5: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage - 11, // 6: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage - 9, // 7: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain - 10, // 8: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig - 12, // 9: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig - 6, // 10: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig - 13, // 11: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy - 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 + 8, // 1: xray.app.proxyman.SniffingConfig.ips_excluded:type_name -> xray.common.geodata.IPRule + 9, // 2: xray.app.proxyman.ReceiverConfig.port_list:type_name -> xray.common.net.PortList + 10, // 3: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain + 11, // 4: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig + 1, // 5: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig + 12, // 6: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage + 12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage + 10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain + 11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig + 13, // 10: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig + 6, // 11: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig + 14, // 12: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_app_proxyman_config_proto_init() } diff --git a/app/proxyman/config.proto b/app/proxyman/config.proto index 36944871..8ccb5132 100644 --- a/app/proxyman/config.proto +++ b/app/proxyman/config.proto @@ -24,6 +24,8 @@ message SniffingConfig { repeated xray.common.geodata.DomainRule domains_excluded = 3; + repeated xray.common.geodata.IPRule ips_excluded = 6; + // Whether should only try to sniff metadata without waiting for client input. // Can be used to support SMTP like protocol where server send the first // message. diff --git a/common/session/session.go b/common/session/session.go index cec9f971..b5f163ff 100644 --- a/common/session/session.go +++ b/common/session/session.go @@ -80,6 +80,7 @@ type Outbound struct { // SniffingRequest controls the behavior of content sniffing. They are from inbound config. Read-only type SniffingRequest struct { ExcludeForDomain geodata.DomainMatcher + ExcludeForIP geodata.IPMatcher OverrideDestinationForProtocol []string Enabled bool MetadataOnly bool diff --git a/infra/conf/xray.go b/infra/conf/xray.go index a96da678..fceda67f 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -55,37 +55,44 @@ type SniffingConfig struct { Enabled bool `json:"enabled"` DestOverride StringList `json:"destOverride"` DomainsExcluded StringList `json:"domainsExcluded"` + IPsExcluded StringList `json:"ipsExcluded"` MetadataOnly bool `json:"metadataOnly"` RouteOnly bool `json:"routeOnly"` } // Build implements Buildable. func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { - var p []string + var protocols []string for _, protocol := range c.DestOverride { switch strings.ToLower(protocol) { case "http": - p = append(p, "http") + protocols = append(protocols, "http") case "tls", "https", "ssl": - p = append(p, "tls") + protocols = append(protocols, "tls") case "quic": - p = append(p, "quic") + protocols = append(protocols, "quic") case "fakedns", "fakedns+others": - p = append(p, "fakedns") + protocols = append(protocols, "fakedns") default: return nil, errors.New("unknown protocol: ", protocol) } } - d, err := geodata.ParseDomainRules(c.DomainsExcluded, geodata.Domain_Substr) + domains, err := geodata.ParseDomainRules(c.DomainsExcluded, geodata.Domain_Substr) + if err != nil { + return nil, err + } + + ips, err := geodata.ParseIPRules(c.IPsExcluded) if err != nil { return nil, err } return &proxyman.SniffingConfig{ Enabled: c.Enabled, - DestinationOverride: p, - DomainsExcluded: d, + DestinationOverride: protocols, + DomainsExcluded: domains, + IpsExcluded: ips, MetadataOnly: c.MetadataOnly, RouteOnly: c.RouteOnly, }, nil diff --git a/infra/conf/xray_test.go b/infra/conf/xray_test.go index 645c7cb1..d415f937 100644 --- a/infra/conf/xray_test.go +++ b/infra/conf/xray_test.go @@ -163,6 +163,7 @@ func TestSniffingConfig_Build(t *testing.T) { Enabled: true, DestOverride: StringList{"http", "tls"}, DomainsExcluded: StringList{"full:api.example.com", "domain:blocked.example", "regexp:^test[0-9]+\\.internal$"}, + IPsExcluded: StringList{"192.168.1.1", "2001:db8::/32"}, MetadataOnly: true, RouteOnly: true, } @@ -181,6 +182,9 @@ func TestSniffingConfig_Build(t *testing.T) { if len(built.DomainsExcluded) != 3 { t.Fatalf("SniffingConfig.Build() produced %d domain rules", len(built.DomainsExcluded)) } + if len(built.IpsExcluded) != 2 { + t.Fatalf("SniffingConfig.Build() produced %d ip rules", len(built.IpsExcluded)) + } want := []struct { ruleType geodata.Domain_Type @@ -199,6 +203,23 @@ func TestSniffingConfig_Build(t *testing.T) { t.Fatalf("SniffingConfig.Build() produced wrong rule at index %d: got (%v, %q), want (%v, %q)", i, rule.Type, rule.Value, tc.ruleType, tc.value) } } + + wantIPs := []struct { + ip []byte + prefix uint32 + }{ + {ip: []byte(net.ParseAddress("192.168.1.1").IP()), prefix: 32}, + {ip: []byte(net.ParseAddress("2001:db8::").IP()), prefix: 32}, + } + for i, tc := range wantIPs { + rule := built.IpsExcluded[i].GetCustom() + if rule == nil { + t.Fatalf("SniffingConfig.Build() produced a non-custom ip rule at index %d", i) + } + if !reflect.DeepEqual(rule.Ip, tc.ip) || rule.Prefix != tc.prefix { + t.Fatalf("SniffingConfig.Build() produced wrong ip rule at index %d: got (%v, %d), want (%v, %d)", i, rule.Ip, rule.Prefix, tc.ip, tc.prefix) + } + } } func TestMuxConfig_Build(t *testing.T) {