index.ts
1 import { Router } from 'itty-router'; 2 import { Buffer } from 'node:buffer'; 3 import base64url from 'base64url'; 4 import * as dnsPacket from '@dnsquery/dns-packet'; 5 import Config from '../config.json'; 6 import Resolvers from '../resolvers.json'; 7 import Package from '../package-lock.json'; 8 9 const router = Router(); 10 11 Array.prototype.sample = function(){ 12 return this[Math.floor(Math.random()*this.length)]; 13 } 14 15 Array.prototype.sampleN = function(n: any) { 16 var result = new Array(n), 17 len = this.length, 18 taken = new Array(len); 19 if (n > len) 20 throw new RangeError("getRandom: more elements taken than available"); 21 while (n--) { 22 var x = Math.floor(Math.random() * len); 23 result[n] = this[x in taken ? taken[x] : x]; 24 taken[x] = --len in taken ? taken[len] : len; 25 } 26 return result; 27 } 28 29 function toRcode(code: any) { 30 switch (code.toUpperCase()) { 31 case 'NOERROR': return 0 32 case 'FORMERR': return 1 33 case 'SERVFAIL': return 2 34 case 'NXDOMAIN': return 3 35 case 'NOTIMP': return 4 36 case 'REFUSED': return 5 37 case 'YXDOMAIN': return 6 38 case 'YXRRSET': return 7 39 case 'NXRRSET': return 8 40 case 'NOTAUTH': return 9 41 case 'NOTZONE': return 10 42 case 'RCODE_11': return 11 43 case 'RCODE_12': return 12 44 case 'RCODE_13': return 13 45 case 'RCODE_14': return 14 46 case 'RCODE_15': return 15 47 } 48 return 0 49 } 50 51 function toTypes(type: any) { 52 switch (type.toUpperCase()) { 53 case 'A': return 1 54 case 'NULL': return 10 55 case 'AAAA': return 28 56 case 'AFSDB': return 18 57 case 'APL': return 42 58 case 'CAA': return 257 59 case 'CDNSKEY': return 60 60 case 'CDS': return 59 61 case 'CERT': return 37 62 case 'CNAME': return 5 63 case 'DHCID': return 49 64 case 'DLV': return 32769 65 case 'DNAME': return 39 66 case 'DNSKEY': return 48 67 case 'DS': return 43 68 case 'HIP': return 55 69 case 'HINFO': return 13 70 case 'IPSECKEY': return 45 71 case 'KEY': return 25 72 case 'KX': return 36 73 case 'LOC': return 29 74 case 'MX': return 15 75 case 'NAPTR': return 35 76 case 'NS': return 2 77 case 'NSEC': return 47 78 case 'NSEC3': return 50 79 case 'NSEC3PARAM': return 51 80 case 'PTR': return 12 81 case 'RRSIG': return 46 82 case 'RP': return 17 83 case 'SIG': return 24 84 case 'SOA': return 6 85 case 'SPF': return 99 86 case 'SRV': return 33 87 case 'SSHFP': return 44 88 case 'TA': return 32768 89 case 'TKEY': return 249 90 case 'TLSA': return 52 91 case 'TSIG': return 250 92 case 'TXT': return 16 93 case 'AXFR': return 252 94 case 'IXFR': return 251 95 case 'OPT': return 41 96 case 'ANY': return 255 97 case '*': return 255 98 } 99 if (type.toUpperCase().startsWith('UNKNOWN_')) return parseInt(name.slice(8)) 100 return 0 101 } 102 103 async function getDNSResponse(url: any) { 104 let p: any = new URL(url).hostname; 105 let r: any = await fetch(url, { 106 headers: { 107 'Content-Type': 'application/dns-message' 108 } 109 }) 110 111 if (r.status !== 200) throw Promise.reject(`Encountered a non 200 response from ${p}`); 112 return r; 113 } 114 115 function chooseResolvers(resolvers: any, q: any, n: any = 3) { 116 let p = []; 117 if (resolvers.length > n) { 118 for (let r of resolvers.sampleN(n)) { 119 p.push(getDNSResponse(`${Resolvers[r]}?dns=${q}`)) 120 } 121 } 122 else { 123 // Otherwise, pick one 124 p.push(getDNSResponse(`${Resolvers[resolvers.sample()]}?dns=${q}`)) 125 } 126 127 return p; 128 } 129 130 function getRandomInt (min: any, max: any) { 131 return Math.floor(Math.random() * (max - min + 1)) + min 132 } 133 134 router.all('/resolve', async (request, env, context) => { 135 // First, grab some request information 136 let url: any = new URL(request.url) 137 138 // Now, we refuse anything that isn't GET or POST 139 if (!['GET', 'POST'].includes(request.method)) { 140 return new Response('Not Found.', { status: 404 }) 141 } 142 143 let name: any; 144 let rrtype: any = 'A'; 145 146 if (request.method == 'GET') { 147 if (request.query.name) name = request.query.name || null; 148 if (request.query.type) rrtype = request.query.type.toUpperCase(); 149 150 if (name == null) return new Response('Missing name in ?name=', { status: 400 }) 151 } 152 153 if (!['A', 'AAAA', 'DNSKEY', 'MX', 'SRV', 'TXT'].includes(rrtype)) return new Response('Unsupported rrtype', { status: 400 }) 154 155 // Next, we need to prepare a query 156 let query: any = dnsPacket.encode({ 157 type: 'query', 158 id: getRandomInt(1, 65534), 159 flags: dnsPacket.RECURSION_DESIRED, 160 questions: [{ 161 type: rrtype, 162 name: name 163 }] 164 }) 165 query = base64url(query); 166 167 // Next, we prepare to send it on, first pick a resolver (by default, we use the default) 168 let resolver: any = Config['default'].resolvers 169 if (Config[url.hostname]) { 170 // Check now for a resolvers set for the hostname the request came in on 171 resolver = Config[url.hostname].resolvers 172 } 173 174 let providers = chooseResolvers(resolver, query); 175 176 // And send it off 177 let answer: any; 178 try { 179 answer = await Promise.any(providers); 180 } 181 catch(e: any) { 182 return new Response('We encountered a server error. Please try again later', { status: 500 }) 183 } 184 185 // Once we have an answer, we read it in 186 let decoded: any = await answer.arrayBuffer(); 187 decoded = Buffer.from(decoded); 188 189 // And next, we decode it 190 decoded = dnsPacket.decode(decoded); 191 192 // Now, we need to prepare the response 193 let resp: any = {} 194 195 // Initially, did the query even work? 196 resp.Status = toRcode(decoded.rcode); 197 198 // Next, we'll add some flags 199 for (let key of Object.keys(decoded)) { 200 if (key.includes('flag_')) { 201 if (['AD', 'CD', 'RA', 'RD', 'TC'].includes(key.replaceAll('flag_', '').toUpperCase())) { 202 resp[key.replaceAll('flag_', '').toUpperCase()] = decoded[key] 203 } 204 } 205 } 206 207 // And the question 208 resp.Question = []; 209 for (let q of decoded.questions) { 210 resp.Question.push({ 211 'name': `${q.name}.`, 212 'type': toTypes(q.type) 213 }) 214 }; 215 216 217 // Now, we determine if there is an answer to give 218 if (decoded.answers.length > 0) { 219 resp.Answer = []; 220 for (let ans of decoded.answers) { 221 222 let r: any = { 223 'name': `${ans.name}.`, 224 'type': toTypes(ans.type), 225 'TTL': ans.ttl, 226 'data': ans.data 227 } 228 229 if (['DNSKEY'].includes(ans.type)) r.data = `${ans.data.flags} ${ans.data.algorithm} ${btoa(String.fromCharCode.apply(null, ans.data.key))}`; 230 if (['TXT'].includes(ans.type)) r.data = ans.data[0].toString() 231 if (['SRV'].includes(ans.type)) r.data = `${ans.data.priority} ${ans.data.weight} ${ans.data.port} ${ans.data.target}.` 232 233 resp.Answer.push(r) 234 } 235 } 236 if (decoded.answers.length == 0) { 237 resp.Authority = []; 238 for (let auth of decoded.authorities) { 239 resp.Authority.push({ 240 'name': auth.name, 241 'type': toTypes(auth.type), 242 'TTL': auth.ttl, 243 'data': `${auth.data.mname}. ${auth.data.rname}. ${auth.data.serial} ${auth.data.refresh} ${auth.data.retry} ${auth.data.expire} ${auth.data.minimum}` 244 }) 245 } 246 } 247 248 // And a comment from where it came from 249 let prov: any = new URL(answer.url).hostname; 250 resp.Comment = `Response from ${prov}` 251 252 return new Response(JSON.stringify(resp), { headers: { 'Content-Type': 'application/json'}}) 253 }) 254 255 router.all('/dns-query', async (request, env, context) => { 256 // First, grab some request information 257 let url: any = new URL(request.url) 258 259 // Now, we refuse anything that isn't GET or POST 260 if (!['GET', 'POST'].includes(request.method)) { 261 return new Response('Not Found.', { status: 404 }) 262 } 263 264 // And grab the question 265 let q: any = null; 266 if (request.method == 'GET') { 267 if (request.query.dns) { 268 q = request.query.dns; 269 } 270 else { 271 return new Response('Missing query in ?dns=', { status: 400 }) 272 } 273 } 274 if (request.method == 'POST') { 275 q = await request.arrayBuffer(); 276 q = Buffer.from(q); 277 } 278 279 // Now, to validate the payload 280 let t: any; 281 try { 282 t = Buffer.from(q, 'base64'); 283 t = dnsPacket.decode(t); 284 } 285 catch(e: any) { 286 return new Response('Invalid query', { status: 500 }) 287 } 288 289 // Next, we prepare to send it on, first pick a resolver (by default, we use the default) 290 let resolver: any = Config['default'].resolvers 291 if (Object.keys(Config).includes(url.hostname)) { 292 // Check now for a resolvers set for the hostname the request came in on 293 resolver = Config[url.hostname].resolvers 294 } 295 296 if (request.method == 'POST') q = base64url(q); 297 let providers = chooseResolvers(resolver, q); 298 299 // And send it off 300 let answer: any; 301 let a: any; 302 try { 303 answer = await Promise.any(providers); 304 a = await answer.arrayBuffer(); 305 } 306 catch(e: any) { 307 // So if we get here, something happened, so we'll try and build our own response 308 return new Response(`We encountered an error while performing this lookup: ${e}`, { status: 500 }); 309 } 310 311 // And if we need a debug issue 312 if (request.url.includes('?debug')) console.log(new URL(answer.url).hostname) 313 return new Response(a, { 314 headers: { 315 'Content-Type': answer.headers.get('Content-Type'), 316 'X-Provider': new URL(answer.url).hostname 317 }, 318 status: answer.status 319 }) 320 }) 321 322 router.get('/dns-providers', async (request) => { 323 324 // First, grab some request information 325 let url: any = new URL(request.url); 326 327 // Now, prepare a payload 328 let resp: any = { 329 'providers': [] 330 } 331 332 // Next, we prepare to send it on, first pick a resolver (set to our default set) 333 let resolver: any = Config['default'].resolvers; 334 if (Config[url.hostname]) { 335 // Check now for a resolvers set for the hostname the request came in on 336 resolver = Config[url.hostname].resolvers 337 } 338 339 // Add each provider to the response, so they can be seen 340 for (let r of resolver) { 341 resp.providers.push(Resolvers[r]) 342 } 343 344 // And return that data 345 return new Response(JSON.stringify(resp, null, 2), { headers: {'Content-Type': 'application/json'}}) 346 }) 347 348 router.get('/version', async (request) => { 349 return new Response(Package.version, { 350 headers: { 351 'Content-Type': 'text/plain' 352 } 353 }) 354 }); 355 356 router.get('/', async (request) => { 357 // First, we grab the hostname they asked for 358 let hostname: any = new URL(request.url).hostname 359 let resolver: any = Config['default'].resolvers; 360 if (Config[hostname]) { 361 // Check now for a resolvers set for the hostname the request came in on 362 resolver = Config[hostname].resolvers; 363 } 364 365 // Now to grab the resolver URLs 366 let resolvers: any = []; 367 for (let r of resolver) { 368 resolvers.push(Resolvers[r]) 369 } 370 371 // This is going to be an amazing hack so I don't have to mess with KV 372 let data: any = await fetch('https://mydns.network/_resolver.html', { 373 cf: { 374 cacheTtl: 90, 375 cacheEverything: true, 376 } 377 }) 378 data = await data.text() 379 380 // And now we make some changes to the stored HTML 381 data = data.replaceAll('[HOSTNAME]', hostname); 382 data = data.replaceAll('[NAME]', hostname.replace('.mydns.network', '')); 383 data = data.replaceAll('[RESOLVERS]', resolvers.join('\n')) 384 385 // And craft a new response 386 return new Response(data, { 387 headers: { 388 'Content-Type': 'text/html' 389 } 390 }) 391 }); 392 393 router.all("*", () => new Response("404, not found!", { status: 404 })) 394 395 export default { 396 fetch: router.handle 397 }