|
| 1 | +{ EventEmitter } = require'events' |
| 2 | +{ createSocket } = require('dgram') |
| 3 | +{ isIPv6 } = require('net') |
| 4 | + |
| 5 | +dns = require('dns') |
| 6 | + |
| 7 | +bitSlice = (b, offset, length) -> |
| 8 | + b >>> 7 - (offset + length - 1) & ~(0xff << length) |
| 9 | + |
| 10 | +bufferify = (ip) -> |
| 11 | + if isIPv6 ip |
| 12 | + bufferifyV6 ip |
| 13 | + else bufferifyV4 ip |
| 14 | + |
| 15 | +bufferifyV4 = (ip) -> |
| 16 | + ip = ip.split('.').map (n) -> parseInt n, 10 |
| 17 | + |
| 18 | + result = 0 |
| 19 | + base = 1 |
| 20 | + |
| 21 | + i = ip.length - 1 |
| 22 | + |
| 23 | + while i >= 0 |
| 24 | + result += ip[i] * base |
| 25 | + base *= 256 |
| 26 | + i-- |
| 27 | + |
| 28 | + buf = Buffer.alloc(4) |
| 29 | + buf.writeUInt32BE result |
| 30 | + buf |
| 31 | + |
| 32 | +bufferifyV6 = (rawIp) -> |
| 33 | + |
| 34 | + countColons = (x) -> |
| 35 | + n = 0 |
| 36 | + x.replace /:/g, (c) -> n++ |
| 37 | + n |
| 38 | + |
| 39 | + ip = rawIp.replace(/\/\d{1,3}(?=%|$)/, '').replace(/%.*$/, '') |
| 40 | + |
| 41 | + hexIp = ip |
| 42 | + .replace /::/, (two) -> ':' + Array(7 - countColons(ip) + 1).join(':') + ':' |
| 43 | + .split ':' |
| 44 | + .map (x) -> Array(4 - (x.length)).fill('0').join('') + x |
| 45 | + .join('') |
| 46 | + |
| 47 | + Buffer.from hexIp, 'hex' |
| 48 | + |
| 49 | +domainify = (qname) -> |
| 50 | + parts = [] |
| 51 | + |
| 52 | + i = 0 |
| 53 | + |
| 54 | + while i < qname.length and qname[i] |
| 55 | + length = qname[i] |
| 56 | + offset = i + 1 |
| 57 | + parts.push qname.slice(offset, offset + length).toString() |
| 58 | + |
| 59 | + i = offset + length |
| 60 | + |
| 61 | + parts.join '.' |
| 62 | + |
| 63 | +qnameify = (domain) -> |
| 64 | + qname = Buffer.alloc(domain.length + 2) |
| 65 | + offset = 0 |
| 66 | + |
| 67 | + domain = domain.split('.') |
| 68 | + |
| 69 | + i = 0 |
| 70 | + |
| 71 | + while i < domain.length |
| 72 | + qname[offset] = domain[i].length |
| 73 | + qname.write domain[i], offset + 1, domain[i].length, 'ascii' |
| 74 | + |
| 75 | + offset += qname[offset] + 1 |
| 76 | + i++ |
| 77 | + |
| 78 | + qname[qname.length - 1] = 0 |
| 79 | + qname |
| 80 | + |
| 81 | +functionify = (val) -> |
| 82 | + (addr, callback) -> |
| 83 | + callback null, val |
| 84 | + |
| 85 | +parse = (buf) -> |
| 86 | + header = {} |
| 87 | + question = {} |
| 88 | + |
| 89 | + b = buf.slice(2, 3).toString('binary', 0, 1).charCodeAt(0) |
| 90 | + |
| 91 | + header.id = buf.slice(0, 2) |
| 92 | + header.qr = bitSlice(b, 0, 1) |
| 93 | + header.opcode = bitSlice(b, 1, 4) |
| 94 | + header.aa = bitSlice(b, 5, 1) |
| 95 | + header.tc = bitSlice(b, 6, 1) |
| 96 | + header.rd = bitSlice(b, 7, 1) |
| 97 | + |
| 98 | + b = buf.slice(3, 4).toString('binary', 0, 1).charCodeAt(0) |
| 99 | + |
| 100 | + header.ra = bitSlice(b, 0, 1) |
| 101 | + header.z = bitSlice(b, 1, 3) |
| 102 | + header.rcode = bitSlice(b, 4, 4) |
| 103 | + header.qdcount = buf.slice(4, 6) |
| 104 | + header.ancount = buf.slice(6, 8) |
| 105 | + header.nscount = buf.slice(8, 10) |
| 106 | + header.arcount = buf.slice(10, 12) |
| 107 | + |
| 108 | + question.qname = buf.slice(12, buf.length - 4) |
| 109 | + question.qtype = buf.slice(buf.length - 4, buf.length - 2) |
| 110 | + question.qclass = buf.slice(buf.length - 2, buf.length) |
| 111 | + |
| 112 | + { header, question } |
| 113 | + |
| 114 | +responseBuffer = (query) -> |
| 115 | + question = query.question |
| 116 | + header = query.header |
| 117 | + qname = question.qname |
| 118 | + |
| 119 | + offset = 16 + qname.length |
| 120 | + |
| 121 | + length = offset |
| 122 | + i = 0 |
| 123 | + |
| 124 | + while i < query.rr.length |
| 125 | + length += query.rr[i].qname.length + 10 |
| 126 | + i++ |
| 127 | + |
| 128 | + buf = Buffer.alloc(length) |
| 129 | + |
| 130 | + header.id.copy buf, 0, 0, 2 |
| 131 | + |
| 132 | + buf[2] = 0x00 | header.qr << 7 | header.opcode << 3 | header.aa << 2 | header.tc << 1 | header.rd |
| 133 | + buf[3] = 0x00 | header.ra << 7 | header.z << 4 | header.rcode |
| 134 | + |
| 135 | + buf.writeUInt16BE header.qdcount, 4 |
| 136 | + buf.writeUInt16BE header.ancount, 6 |
| 137 | + buf.writeUInt16BE header.nscount, 8 |
| 138 | + buf.writeUInt16BE header.arcount, 10 |
| 139 | + |
| 140 | + qname.copy buf, 12 |
| 141 | + |
| 142 | + question.qtype.copy buf, 12 + qname.length, question.qtype, 2 |
| 143 | + question.qclass.copy buf, 12 + qname.length + 2, question.qclass, 2 |
| 144 | + |
| 145 | + i = 0 |
| 146 | + |
| 147 | + while i < query.rr.length |
| 148 | + rr = query.rr[i] |
| 149 | + rr.qname.copy buf, offset |
| 150 | + offset += rr.qname.length |
| 151 | + buf.writeUInt16BE rr.qtype, offset |
| 152 | + buf.writeUInt16BE rr.qclass, offset + 2 |
| 153 | + buf.writeUInt32BE rr.ttl, offset + 4 |
| 154 | + buf.writeUInt16BE rr.rdlength, offset + 8 |
| 155 | + buf = Buffer.concat([ |
| 156 | + buf |
| 157 | + rr.rdata |
| 158 | + ]) |
| 159 | + offset += 14 |
| 160 | + i++ |
| 161 | + buf |
| 162 | + |
| 163 | +response = (query, ttl, to) -> |
| 164 | + response = {} |
| 165 | + |
| 166 | + header = response.header = {} |
| 167 | + question = response.question = {} |
| 168 | + rrs = resolve(query.question.qname, ttl, to) |
| 169 | + |
| 170 | + header.id = query.header.id |
| 171 | + header.ancount = rrs.length |
| 172 | + header.qr = 1 |
| 173 | + header.opcode = 0 |
| 174 | + header.aa = 0 |
| 175 | + header.tc = 0 |
| 176 | + header.rd = 1 |
| 177 | + header.ra = 0 |
| 178 | + header.z = 0 |
| 179 | + header.rcode = 0 |
| 180 | + header.qdcount = 1 |
| 181 | + header.nscount = 0 |
| 182 | + header.arcount = 0 |
| 183 | + |
| 184 | + question.qname = query.question.qname |
| 185 | + question.qtype = query.question.qtype |
| 186 | + question.qclass = query.question.qclass |
| 187 | + |
| 188 | + response.rr = rrs |
| 189 | + |
| 190 | + responseBuffer response |
| 191 | + |
| 192 | +resolve = (qname, ttl, to) -> |
| 193 | + r = {} |
| 194 | + r.qname = qname |
| 195 | + r.qtype = if to.length == 4 then 1 else 28 |
| 196 | + |
| 197 | + r.qclass = 1 |
| 198 | + r.ttl = ttl |
| 199 | + r.rdlength = to.length |
| 200 | + r.rdata = to |
| 201 | + |
| 202 | + [ r ] |
| 203 | + |
| 204 | +lookup = (addr, callback) -> |
| 205 | + if net.isIP(addr) |
| 206 | + return callback(null, addr) |
| 207 | + |
| 208 | + dns.lookup addr, callback |
| 209 | + |
| 210 | +class Server extends EventEmitter |
| 211 | + constructor: (proxy = '8.8.8.8') -> |
| 212 | + super |
| 213 | + |
| 214 | + @_socket = createSocket if isIPv6(proxy) then 'udp6' else 'udp4' |
| 215 | + |
| 216 | + routes = [] |
| 217 | + |
| 218 | + @_socket.on 'message', (message, rinfo) => |
| 219 | + query = parse message |
| 220 | + domain = domainify query.question.qname |
| 221 | + |
| 222 | + routeData = { domain, rinfo } |
| 223 | + |
| 224 | + @emit 'resolve', routeData |
| 225 | + |
| 226 | + respond = (buf) => |
| 227 | + @_socket.send buf, 0, buf.length, rinfo.port, rinfo.address |
| 228 | + |
| 229 | + onerror = (err) => |
| 230 | + @emit 'error', err |
| 231 | + |
| 232 | + onproxy = -> |
| 233 | + sock = createSocket if isIPv6(proxy) then 'udp6' else 'udp4' |
| 234 | + sock.send message, 0, message.length, 53, proxy |
| 235 | + |
| 236 | + sock.on 'error', onerror |
| 237 | + |
| 238 | + sock.on 'message', (response) -> |
| 239 | + respond response |
| 240 | + sock.close() |
| 241 | + |
| 242 | + i = 0 |
| 243 | + |
| 244 | + while i < routes.length |
| 245 | + if routes[i].pattern.test(domain) |
| 246 | + route = routes[i].route |
| 247 | + break |
| 248 | + i++ |
| 249 | + |
| 250 | + if not route |
| 251 | + return onproxy() |
| 252 | + |
| 253 | + route routeData, (err, to) => |
| 254 | + if typeof to == 'string' |
| 255 | + toIp = to |
| 256 | + ttl = 1 |
| 257 | + else |
| 258 | + toIp = to.ip |
| 259 | + ttl = to.ttl |
| 260 | + |
| 261 | + if err |
| 262 | + return onerror(err) |
| 263 | + |
| 264 | + if !toIp |
| 265 | + return onproxy() |
| 266 | + |
| 267 | + lookup toIp, (err, addr) => |
| 268 | + if err |
| 269 | + return onerror(err) |
| 270 | + |
| 271 | + @emit 'route', domain, addr |
| 272 | + |
| 273 | + respond response(query, ttl, bufferify(addr)) |
| 274 | + |
| 275 | + route: (pattern, route) -> |
| 276 | + if Array.isArray pattern |
| 277 | + pattern.forEach (item) => |
| 278 | + @route item, route |
| 279 | + |
| 280 | + return @ |
| 281 | + |
| 282 | + if typeof pattern == 'function' |
| 283 | + return @route('*', pattern) |
| 284 | + |
| 285 | + if typeof route == 'string' |
| 286 | + return @route(pattern, functionify(route)) |
| 287 | + |
| 288 | + pattern = if pattern is '*' |
| 289 | + /.?/ |
| 290 | + else |
| 291 | + new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*\\\./g, '(.+)\\.') + '$', 'i') |
| 292 | + |
| 293 | + routes.push { pattern, route } |
| 294 | + |
| 295 | + @ |
| 296 | + |
| 297 | + listen: (port) -> |
| 298 | + @@_socket.bind port or 53 |
| 299 | + |
| 300 | + @ |
| 301 | + |
| 302 | + close: (callback) -> |
| 303 | + @@_socket.close callback |
| 304 | + |
| 305 | + @ |
0 commit comments