@@ -93,7 +93,23 @@ private void GenDnsServers()
9393
9494 foreach ( var kvp in Utils . ParseHostsToDictionary ( simpleDnsItem . Hosts ) )
9595 {
96- hostsDns . predefined [ kvp . Key ] = kvp . Value . Where ( Utils . IsIpAddress ) . ToList ( ) ;
96+ // only allow full match
97+ // like example.com and full:example.com,
98+ // but not domain:example.com, keyword:example.com or regex:example.com etc.
99+ var testRule = new Rule4Sbox ( ) ;
100+ if ( ! ParseV2Domain ( kvp . Key , testRule ) )
101+ {
102+ continue ;
103+ }
104+ if ( testRule . domain_keyword ? . Count > 0 && ! kvp . Key . Contains ( ':' ) )
105+ {
106+ testRule . domain = testRule . domain_keyword ;
107+ testRule . domain_keyword = null ;
108+ }
109+ if ( testRule . domain ? . Count == 1 )
110+ {
111+ hostsDns . predefined [ testRule . domain . First ( ) ] = kvp . Value . Where ( Utils . IsIpAddress ) . ToList ( ) ;
112+ }
97113 }
98114
99115 foreach ( var host in hostsDns . predefined )
@@ -179,44 +195,66 @@ private void GenDnsRules()
179195 foreach ( var kvp in Utils . ParseHostsToDictionary ( simpleDnsItem . Hosts ) )
180196 {
181197 var predefined = kvp . Value . First ( ) ;
182- if ( predefined . IsNullOrEmpty ( ) || Utils . IsIpAddress ( predefined ) )
198+ if ( predefined . IsNullOrEmpty ( ) )
199+ {
200+ continue ;
201+ }
202+ var rule = new Rule4Sbox ( )
203+ {
204+ query_type = [ 1 , 5 , 28 ] , // A, CNAME and AAAA
205+ action = "predefined" ,
206+ rcode = "NOERROR" ,
207+ } ;
208+ if ( ! ParseV2Domain ( kvp . Key , rule ) )
183209 {
184210 continue ;
185211 }
212+ // see: https://xtls.github.io/en/config/dns.html#dnsobject
213+ // The matching format (domain:, full:, etc.) is the same as the domain
214+ // in the commonly used Routing System. The difference is that without a prefix,
215+ // it defaults to using the full: prefix (similar to the common hosts file syntax).
216+ if ( rule . domain_keyword ? . Count > 0 && ! kvp . Key . Contains ( ':' ) )
217+ {
218+ rule . domain = rule . domain_keyword ;
219+ rule . domain_keyword = null ;
220+ }
221+ // example.com #0 -> example.com with NOERROR
186222 if ( predefined . StartsWith ( '#' ) && int . TryParse ( predefined . AsSpan ( 1 ) , out var rcode ) )
187223 {
188- // xray syntactic sugar for predefined
189- // etc. #0 -> NOERROR
190- _coreConfig . dns . rules . Add ( new ( )
224+ rule . rcode = rcode switch
191225 {
192- query_type = [ 1 , 28 ] ,
193- domain = [ kvp . Key ] ,
194- action = "predefined" ,
195- rcode = rcode switch
196- {
197- 0 => "NOERROR" ,
198- 1 => "FORMERR" ,
199- 2 => "SERVFAIL" ,
200- 3 => "NXDOMAIN" ,
201- 4 => "NOTIMP" ,
202- 5 => "REFUSED" ,
203- _ => "NOERROR" ,
204- } ,
205- } ) ;
206- continue ;
226+ 0 => "NOERROR" ,
227+ 1 => "FORMERR" ,
228+ 2 => "SERVFAIL" ,
229+ 3 => "NXDOMAIN" ,
230+ 4 => "NOTIMP" ,
231+ 5 => "REFUSED" ,
232+ _ => "NOERROR" ,
233+ } ;
207234 }
208- // CNAME record
209- Rule4Sbox rule = new ( )
235+ else if ( Utils . IsDomain ( predefined ) )
210236 {
211- query_type = [ 1 , 28 ] ,
212- action = "predefined" ,
213- rcode = "NOERROR" ,
214- answer = [ $ "*. IN CNAME { predefined } ."] ,
215- } ;
216- if ( ParseV2Domain ( kvp . Key , rule ) )
237+ // example.com CNAME target.com -> example.com with CNAME target.com
238+ rule . answer = new List < string > { $ "*. IN CNAME { predefined } ." } ;
239+ }
240+ else if ( Utils . IsIpAddress ( predefined ) && ( rule . domain ? . Count ?? 0 ) == 0 )
241+ {
242+ // not full match, but an IP address, treat it as predefined answer
243+ if ( Utils . IsIpv6 ( predefined ) )
244+ {
245+ rule . answer = new List < string > { $ "*. IN AAAA { predefined } " } ;
246+
247+ }
248+ else
249+ {
250+ rule . answer = new List < string > { $ "*. IN A { predefined } " } ;
251+ }
252+ }
253+ else
217254 {
218- _coreConfig . dns . rules . Add ( rule ) ;
255+ continue ;
219256 }
257+ _coreConfig . dns . rules . Add ( rule ) ;
220258 }
221259
222260 if ( simpleDnsItem . BlockBindingQuery == true )
0 commit comments