diff --git a/adapter/inbound.go b/adapter/inbound.go index d07d2810..6e478ba3 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -46,6 +46,10 @@ type InboundContext struct { SourceGeoIPCode string GeoIPCode string ProcessInfo *process.Info + + // dns cache + + QueryType uint16 } type inboundContextKey struct{} diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index eae988ed..5132253b 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -9,6 +9,11 @@ "mixed-in" ], "ip_version": 6, + "query_type": [ + "A", + "HTTPS", + 32768 + ], "network": "tcp", "auth_user": [ "usera", @@ -119,6 +124,10 @@ Tags of [Inbound](/configuration/inbound). Not limited if empty. +#### query_type + +DNS query type. Values can be integers or type name strings. + #### network `tcp` or `udp`. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 9f181438..f3ea1c01 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -9,6 +9,11 @@ "mixed-in" ], "ip_version": 6, + "query_type": [ + "A", + "HTTPS", + 32768 + ], "network": "tcp", "auth_user": [ "usera", @@ -118,6 +123,10 @@ 默认不限制。 +#### query_type + +DNS 查询类型。值可以为整数或者类型名称字符串。 + #### network `tcp` 或 `udp`。 diff --git a/option/dns.go b/option/dns.go index b232baea..637f2c05 100644 --- a/option/dns.go +++ b/option/dns.go @@ -77,32 +77,33 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error { } type DefaultDNSRule struct { - Inbound Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network string `json:"network,omitempty"` - AuthUser Listable[string] `json:"auth_user,omitempty"` - Protocol Listable[string] `json:"protocol,omitempty"` - Domain Listable[string] `json:"domain,omitempty"` - DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex Listable[string] `json:"domain_regex,omitempty"` - Geosite Listable[string] `json:"geosite,omitempty"` - SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` - SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` - SourcePort Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange Listable[string] `json:"source_port_range,omitempty"` - Port Listable[uint16] `json:"port,omitempty"` - PortRange Listable[string] `json:"port_range,omitempty"` - ProcessName Listable[string] `json:"process_name,omitempty"` - ProcessPath Listable[string] `json:"process_path,omitempty"` - PackageName Listable[string] `json:"package_name,omitempty"` - User Listable[string] `json:"user,omitempty"` - UserID Listable[int32] `json:"user_id,omitempty"` - Outbound Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - Invert bool `json:"invert,omitempty"` - Server string `json:"server,omitempty"` - DisableCache bool `json:"disable_cache,omitempty"` + Inbound Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` + Network string `json:"network,omitempty"` + AuthUser Listable[string] `json:"auth_user,omitempty"` + Protocol Listable[string] `json:"protocol,omitempty"` + Domain Listable[string] `json:"domain,omitempty"` + DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex Listable[string] `json:"domain_regex,omitempty"` + Geosite Listable[string] `json:"geosite,omitempty"` + SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` + SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` + SourcePort Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange Listable[string] `json:"source_port_range,omitempty"` + Port Listable[uint16] `json:"port,omitempty"` + PortRange Listable[string] `json:"port_range,omitempty"` + ProcessName Listable[string] `json:"process_name,omitempty"` + ProcessPath Listable[string] `json:"process_path,omitempty"` + PackageName Listable[string] `json:"package_name,omitempty"` + User Listable[string] `json:"user,omitempty"` + UserID Listable[int32] `json:"user_id,omitempty"` + Outbound Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + Invert bool `json:"invert,omitempty"` + Server string `json:"server,omitempty"` + DisableCache bool `json:"disable_cache,omitempty"` } func (r DefaultDNSRule) IsValid() bool { diff --git a/option/types.go b/option/types.go index 6c5bd8bc..0756b86b 100644 --- a/option/types.go +++ b/option/types.go @@ -8,7 +8,10 @@ import ( "github.com/sagernet/sing-box/common/json" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" ) type ListenAddress netip.Addr @@ -187,3 +190,40 @@ func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error { func (p ListenPrefix) Build() netip.Prefix { return netip.Prefix(p) } + +type DNSQueryType uint16 + +func (t DNSQueryType) MarshalJSON() ([]byte, error) { + typeName, loaded := mDNS.TypeToString[uint16(t)] + if loaded { + return json.Marshal(typeName) + } + return json.Marshal(uint16(t)) +} + +func (t *DNSQueryType) UnmarshalJSON(bytes []byte) error { + var valueNumber uint16 + err := json.Unmarshal(bytes, &valueNumber) + if err == nil { + *t = DNSQueryType(valueNumber) + return nil + } + var valueString string + err = json.Unmarshal(bytes, &valueString) + if err == nil { + queryType, loaded := mDNS.StringToType[valueString] + if loaded { + *t = DNSQueryType(queryType) + return nil + } + } + return E.New("unknown DNS query type: ", string(bytes)) +} + +func DNSQueryTypeToString(queryType uint16) string { + typeName, loaded := mDNS.TypeToString[queryType] + if loaded { + return typeName + } + return F.ToString(queryType) +} diff --git a/route/router_dns.go b/route/router_dns.go index ba39635b..e320daec 100644 --- a/route/router_dns.go +++ b/route/router_dns.go @@ -50,7 +50,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er } ctx, metadata := adapter.AppendContext(ctx) if len(message.Question) > 0 { - switch message.Question[0].Qtype { + metadata.QueryType = message.Question[0].Qtype + switch metadata.QueryType { case mDNS.TypeA: metadata.IPVersion = 4 case mDNS.TypeAAAA: diff --git a/route/rule_dns.go b/route/rule_dns.go index 0f072750..3bfdb729 100644 --- a/route/rule_dns.go +++ b/route/rule_dns.go @@ -71,6 +71,11 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options return nil, E.New("invalid ip version: ", options.IPVersion) } } + if len(options.QueryType) > 0 { + item := NewQueryTypeItem(options.QueryType) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if options.Network != "" { switch options.Network { case N.NetworkTCP, N.NetworkUDP: diff --git a/route/rule_query_type.go b/route/rule_query_type.go new file mode 100644 index 00000000..7b6efdd0 --- /dev/null +++ b/route/rule_query_type.go @@ -0,0 +1,47 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" +) + +var _ RuleItem = (*QueryTypeItem)(nil) + +type QueryTypeItem struct { + typeList []uint16 + typeMap map[uint16]bool +} + +func NewQueryTypeItem(typeList []option.DNSQueryType) *QueryTypeItem { + rule := &QueryTypeItem{ + typeList: common.Map(typeList, func(it option.DNSQueryType) uint16 { + return uint16(it) + }), + typeMap: make(map[uint16]bool), + } + for _, userId := range rule.typeList { + rule.typeMap[userId] = true + } + return rule +} + +func (r *QueryTypeItem) Match(metadata *adapter.InboundContext) bool { + if metadata.QueryType == 0 { + return false + } + return r.typeMap[metadata.QueryType] +} + +func (r *QueryTypeItem) String() string { + var description string + pLen := len(r.typeList) + if pLen == 1 { + description = "query_type=" + option.DNSQueryTypeToString(r.typeList[0]) + } else { + description = "query_type=[" + strings.Join(common.Map(r.typeList, option.DNSQueryTypeToString), " ") + "]" + } + return description +}