1111// along with this program. If not, see <http://www.gnu.org/licenses/>.
1212
1313using System . Net ;
14+ using System . Net . Http . Headers ;
1415using System . Net . NetworkInformation ;
1516using System . Net . Sockets ;
1617using TinyDNS . Cache ;
@@ -24,14 +25,17 @@ public sealed class DNSResolver
2425 public const int PORT = 53 ;
2526 private readonly HashSet < IPAddress > globalNameservers = [ ] ;
2627 private ResolverCache cache = new ResolverCache ( ) ;
27- public DNSResolver ( )
28+ private ResolutionMode resolutionMode ;
29+ public DNSResolver ( ResolutionMode mode = ResolutionMode . InsecureOnly )
2830 {
31+ this . resolutionMode = mode ;
2932 ReloadNameservers ( ) ;
3033 NetworkChange . NetworkAddressChanged += ( s , e ) => ReloadNameservers ( ) ;
3134 }
3235
33- public DNSResolver ( List < IPAddress > nameservers )
36+ public DNSResolver ( List < IPAddress > nameservers , ResolutionMode mode = ResolutionMode . InsecureOnly )
3437 {
38+ this . resolutionMode = mode ;
3539 foreach ( IPAddress nameserver in nameservers )
3640 this . globalNameservers . Add ( nameserver ) ;
3741 }
@@ -106,6 +110,7 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
106110 {
107111 byte [ ] addressBytes = address . GetAddressBytes ( ) ;
108112 List < string > host ;
113+ bool privateQuery = IsPrivate ( address , addressBytes ) ;
109114 if ( address . AddressFamily == AddressFamily . InterNetwork )
110115 {
111116 host = new List < string > ( 6 ) ;
@@ -121,13 +126,13 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
121126 for ( int i = addressBytes . Length - 1 ; i >= 0 ; i -- )
122127 {
123128 string hex = addressBytes [ i ] . ToString ( "x2" ) ;
124- host . Add ( hex . Substring ( 1 , 1 ) ) ;
129+ host . Add ( hex . Substring ( 1 , 1 ) ) ;
125130 host . Add ( hex . Substring ( 0 , 1 ) ) ;
126131 }
127132 host . Add ( "IP6" ) ;
128133 host . Add ( "ARPA" ) ;
129134 }
130- Message ? response = await ResolveQuery ( new QuestionRecord ( host , DNSRecordType . PTR , false ) ) ;
135+ Message ? response = await ResolveQuery ( new QuestionRecord ( host , DNSRecordType . PTR , false ) , privateQuery ) ;
131136 if ( response == null || response . ResponseCode != DNSStatus . OK )
132137 return null ;
133138
@@ -141,15 +146,23 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
141146
142147 public async Task < Message ? > ResolveQuery ( QuestionRecord question )
143148 {
144- return await ResolveQueryInternal ( question , globalNameservers ) ;
149+ bool privateQuery = ( question . Name . Last ( ) == "local" ) ;
150+ return await ResolveQueryInternal ( question , globalNameservers , privateQuery ) ;
145151 }
146152
147- private async Task < Message ? > ResolveQueryInternal ( QuestionRecord question , HashSet < IPAddress > nameservers , int recursionCount = 0 )
153+ private async Task < Message ? > ResolveQuery ( QuestionRecord question , bool privateQuery )
148154 {
155+ return await ResolveQueryInternal ( question , globalNameservers , privateQuery ) ;
156+ }
157+
158+ private async Task < Message ? > ResolveQueryInternal ( QuestionRecord question , HashSet < IPAddress > nameservers , bool privateQuery , int recursionCount = 0 )
159+ {
160+ //Check for excessive recursion
149161 recursionCount ++ ;
150162 if ( recursionCount > 10 )
151163 return null ;
152164
165+ //Check for cache hits
153166 ResourceRecord [ ] ? cacheHits = cache . Search ( question ) ;
154167 if ( cacheHits != null && cacheHits . Length > 0 )
155168 {
@@ -160,6 +173,7 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
160173 return msg ;
161174 }
162175
176+ //Otherwise query the nameserver(s)
163177 Socket ? socket = null ;
164178 try
165179 {
@@ -170,12 +184,34 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
170184
171185 foreach ( IPAddress nsIP in nameservers )
172186 {
187+ //Prevent leaking local domains into the global DNS space
188+ if ( privateQuery && IsPrivate ( nsIP ) )
189+ return null ;
190+
173191 int bytes ;
174192 try
175193 {
176- int len = query . ToBytes ( buffer . Span ) ;
177- await socket . SendToAsync ( buffer . Slice ( 0 , len ) , SocketFlags . None , new IPEndPoint ( nsIP , PORT ) ) ;
178- bytes = await socket . ReceiveAsync ( buffer , SocketFlags . None , new CancellationTokenSource ( 3000 ) . Token ) ;
194+ if ( resolutionMode == ResolutionMode . InsecureOnly )
195+ bytes = await ResolveUDP ( query , buffer , socket , nsIP ) ;
196+ else
197+ {
198+ try
199+ {
200+ bytes = await ResolveHTTPS ( query , buffer , nsIP ) ;
201+ }
202+ catch ( HttpRequestException )
203+ {
204+ if ( resolutionMode == ResolutionMode . SecureOnly )
205+ continue ;
206+ bytes = await ResolveUDP ( query , buffer , socket , nsIP ) ;
207+ }
208+ catch ( OperationCanceledException )
209+ {
210+ if ( resolutionMode == ResolutionMode . SecureOnly )
211+ continue ;
212+ bytes = await ResolveUDP ( query , buffer , socket , nsIP ) ;
213+ }
214+ }
179215 }
180216 catch ( SocketException ) { continue ; }
181217 catch ( OperationCanceledException ) { continue ; }
@@ -192,10 +228,12 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
192228 if ( response . ResponseCode != DNSStatus . OK )
193229 continue ;
194230
195- //Check if we have a valid answer
231+ //Add new info to the cache
196232 cache . Store ( response . Answers ) ;
197233 cache . Store ( response . Authorities ) ;
198234 cache . Store ( response . Additionals ) ;
235+
236+ //Check if we have a valid answer
199237 foreach ( ResourceRecord answer in response . Answers )
200238 {
201239 if ( answer . Type == question . Type )
@@ -213,7 +251,7 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
213251 if ( answer is CNameRecord cname )
214252 {
215253 question . Name = cname . CNameLabels ;
216- return await ResolveQueryInternal ( question , nameservers , recursionCount ) ;
254+ return await ResolveQueryInternal ( question , nameservers , privateQuery , recursionCount ) ;
217255 }
218256 }
219257
@@ -264,7 +302,7 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
264302 }
265303
266304 if ( nextNSIPs . Any ( ) )
267- return await ResolveQueryInternal ( question , nextNSIPs , recursionCount ) ;
305+ return await ResolveQueryInternal ( question , nextNSIPs , privateQuery , recursionCount ) ;
268306 }
269307 }
270308 }
@@ -277,5 +315,51 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)
277315 }
278316 return null ;
279317 }
318+
319+ private static bool IsPrivate ( IPAddress ip , byte [ ] ? addr = null )
320+ {
321+ if ( ip . IsIPv6UniqueLocal || ip . IsIPv6SiteLocal || ip . IsIPv6LinkLocal || IPAddress . IsLoopback ( ip ) )
322+ return true ;
323+ if ( ip . AddressFamily == AddressFamily . InterNetwork )
324+ {
325+ if ( addr == null )
326+ addr = ip . GetAddressBytes ( ) ;
327+ if ( ( addr [ 0 ] == 169 && addr [ 1 ] == 254 ) || ( addr [ 0 ] == 192 && addr [ 1 ] == 168 ) ||
328+ ( addr [ 0 ] == 10 ) || ( addr [ 0 ] == 172 && ( addr [ 1 ] & 0xF0 ) == 0x10 ) )
329+ return true ;
330+ }
331+ return false ;
332+ }
333+
334+ private async Task < int > ResolveUDP ( Message query , Memory < byte > buffer , Socket socket , IPAddress nameserverIP )
335+ {
336+ int len = query . ToBytes ( buffer . Span ) ;
337+ await socket . SendToAsync ( buffer . Slice ( 0 , len ) , SocketFlags . None , new IPEndPoint ( nameserverIP , PORT ) ) ;
338+ return await socket . ReceiveAsync ( buffer , SocketFlags . None , new CancellationTokenSource ( 3000 ) . Token ) ;
339+ }
340+
341+ private async Task < int > ResolveHTTPS ( Message query , Memory < byte > buffer , IPAddress nameserverIP )
342+ {
343+ query . TransactionID = 0 ;
344+ int len = query . ToBytes ( buffer . Span ) ;
345+ using ( HttpClient httpClient = new HttpClient ( ) )
346+ {
347+ ByteArrayContent content = new ByteArrayContent ( buffer . Slice ( 0 , len ) . ToArray ( ) ) ;
348+ content . Headers . ContentType = new MediaTypeHeaderValue ( "application/dns-message" ) ;
349+ string hostname = nameserverIP . ToString ( ) ;
350+ if ( nameserverIP . AddressFamily == AddressFamily . InterNetworkV6 )
351+ hostname = String . Concat ( "[" , hostname , "]" ) ;
352+ HttpRequestMessage request = new HttpRequestMessage ( HttpMethod . Post , $ "https://{ hostname } /dns-query") ;
353+ request . Content = content ;
354+ request . Headers . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/dns-message" ) ) ;
355+ request . Version = new Version ( 2 , 0 ) ;
356+ request . VersionPolicy = HttpVersionPolicy . RequestVersionOrHigher ;
357+ var response = await httpClient . SendAsync ( request , new CancellationTokenSource ( 3000 ) . Token ) ;
358+ response . EnsureSuccessStatusCode ( ) ;
359+ var tempBuff = await response . Content . ReadAsByteArrayAsync ( ) ;
360+ tempBuff . CopyTo ( buffer ) ;
361+ return tempBuff . Length ;
362+ }
363+ }
280364 }
281365}
0 commit comments