纯nodejs简易实现DNS解析服务器

dns-server

A DNS server app written by pure Nodejs for develop and test use. Only support IPv4 now.

To resolve specified domain name to specified IP using keyword-matching algorithm. Useful for frontend developer and tester when involved into a website project.

纯Nodejs写的本地DNS服务器,供开发测试使用。

前端做网站时,本地开发通常是用nginx或nodejs做服务器,然后在浏览器中使用IP地址(127.0.0.1或localhost)来打开网站。但有些功能需要有域名(或二级域名)才能工作,那么就可以用这个工具来做本地解析,对符合关键字的域名查询指向指定的IP。

效果图 effect

请留意各图中的url地址,实际上hursing.com不存在的。Please look at the url and know that the hursing.com does not exist actually.

纯nodejs简易实现DNS解析服务器_第1张图片

纯nodejs简易实现DNS解析服务器_第2张图片

纯nodejs简易实现DNS解析服务器_第3张图片

纯nodejs简易实现DNS解析服务器_第4张图片

使用方法 How to use

按照图示,找到原来的DNS服务器地址,并改成127.0.0.1。Follow the steps, find the original dns server and change it to 127.0.0.1.

Mac

修改前 before change

纯nodejs简易实现DNS解析服务器_第5张图片

修改后 after change:

纯nodejs简易实现DNS解析服务器_第6张图片

Windows

查看原来的dns服务器地址 view the original dns server:

纯nodejs简易实现DNS解析服务器_第7张图片

修改前 before change:

纯nodejs简易实现DNS解析服务器_第8张图片

修改后 after change:

纯nodejs简易实现DNS解析服务器_第9张图片

启动dns服务 launch dns server

打开 openindex.js:

纯nodejs简易实现DNS解析服务器_第10张图片

对上面的代码做修改 modify the code

  • fallbackServer改成原来的DNS服务器地址 change fallbackServer to your original dns server address
  • domain改成你想解析的域名关键字 change domain to the keyword of your expected domain name
  • targetIp改成你想解析到的IP地址 change targetIp to the resolved IP

然后运行 then run node index.js。mac上需要 on mac you have to run sudo node index.js

纯nodejs简易实现DNS解析服务器_第11张图片

实现原理 implement

DNS protocol: https://tools.ietf.org/html/rfc1035

具体请看源码: https://github.com/hursing/dns-server,也就100行代码而已。

可以做更多 You can do more

  • 加入配置文件,可匹配多个关键字 use configuration file for matching multiple keywords
  • 增加命令行参数,不用在源码修改关键字 add command line args support to avoid modifying keyword in source code

源码

以Github的为最新,这里贴一贴供参考

// https://github.com/hursing/dns-server

// The regular expression keyword in domain name.
const domain = /hursing/
// When keyword matched, resolve to this IP.
const targetIp = '127.0.0.1'
// When keyword not matched, use the fallback dns server to resolve.
const fallbackServer = '10.0.0.1'

const dgram = require('dgram')

const server = dgram.createSocket('udp4')

function copyBuffer(src, offset, dst) {
  for (let i = 0; i < src.length; ++i) {
    dst.writeUInt8(src.readUInt8(i), offset + i)
  }
}

function resolve(msg, rinfo) {
  const queryInfo = msg.slice(12)
  const response = Buffer.alloc(28 + queryInfo.length)
  let offset = 0
  const id = msg.slice(0, 2)
  copyBuffer(id, 0, response)  // Transaction ID
  offset += id.length
  response.writeUInt16BE(0x8180, offset)  // Flags
  offset += 2
  response.writeUInt16BE(1, offset)  // Questions
  offset += 2
  response.writeUInt16BE(1, offset)  // Answer RRs
  offset += 2
  response.writeUInt32BE(0, offset)  // Authority RRs & Additional RRs
  offset += 4
  copyBuffer(queryInfo, offset, response)
  offset += queryInfo.length
  response.writeUInt16BE(0xC00C, offset)  // offset to domain name
  offset += 2
  const typeAndClass = msg.slice(msg.length - 4)
  copyBuffer(typeAndClass, offset, response)
  offset += typeAndClass.length
  response.writeUInt32BE(600, offset)  // TTL, in seconds
  offset += 4
  response.writeUInt16BE(4, offset)  // Length of IP
  offset += 2
  targetIp.split('.').forEach(value => {
    response.writeUInt8(parseInt(value), offset)
    offset += 1
  })
  // console.log(response.toString('hex'))
  server.send(response, rinfo.port, rinfo.address, (err) => {
    if (err) {
      console.log(err)
      server.close()
    }
  })
}

function forward(msg, rinfo) {
  const client = dgram.createSocket('udp4')
  client.on('error', (err) => {
    console.log(`client error:\n${err.stack}`)
    client.close()
  })
  client.on('message', (fbMsg, fbRinfo) => {
    server.send(fbMsg, rinfo.port, rinfo.address, (err) => {
      err && console.log(err)
    })
    client.close()
  })
  client.send(msg, 53, fallbackServer, (err) => {
    if (err) {
      console.log(err)
      client.close()
    }
  })
}

function parseHost(msg) {
  let num = msg.readUInt8(0)
  let offset = 1
  let host = ""
  while (num !== 0) {
    host += msg.slice(offset, offset + num).toString()
    offset += num
    num = msg.readUInt8(offset)
    offset += 1
    if (num !== 0) {
      host += '.'
    }
  }
  return host
}

server.on('message', (msg, rinfo) => {
  // console.log(msg.toString('hex'))
  const host = parseHost(msg.slice(12))
  console.log(`receive query: ${host}`)
  if (domain.test(host)) {
    resolve(msg, rinfo)
  } else {
    forward(msg, rinfo)
  }
})

server.on('error', (err) => {
  console.log(`server error:\n${err.stack}`)
  server.close()
})

server.on('listening', () => {
  const address = server.address()
  console.log(`server listening ${address.address}:${address.port}`)
})

// On linux or Mac, run node with sudo. Because port 53 is lower then 1024.
server.bind(53)

你可能感兴趣的:(前端)