美文网首页
NodeJS编写简单的DNS服务器

NodeJS编写简单的DNS服务器

作者: 关爱单身狗成长协会 | 来源:发表于2018-09-13 20:52 被阅读739次

    1.获取响应报文

    首先我们要在网络配置设置DNS服务

    搭建简单的UPD服务

    const dgram = require('dgram');
    const server = dgram.createSocket('udp4');
    
    server.on('message', (msg, rinfo) => {   
        console.log(JSON.stringify(msg));//接收查询 
    })
    
    server.on('error', (err) => {
        console.log('server error:'+err.stack)
        server.close()
    })
    server.on('listening', () => {
        const addr = server.address()
        console.log(`run ${addr.address}:${addr.port}`)
    })
    server.bind(53);
    

    2.添加转发

    转发很简单,接收到DNS解析请求后将请求转发到另外一个服务器上

    const dgram = require('dgram');
    const server = dgram.createSocket('udp4');
    
    const fbSer = '192.168.1.1';//默认DNS服务器
    function forward(msg, rinfo) {
        const client = dgram.createSocket('udp4');
        client.on('error', (err) => {
            console.log(`client error:`+err.stack)
            client.close();
        })
        client.on('message', (fMsg, fbRinfo) => {
            console.log("result:",JSON.stringify(fMsg));//获取响应报文
            server.send(fMsg, rinfo.port, rinfo.address, (err) => {
                err && console.log(err);
            });
            client.close();
        })
        client.send(msg, 53, fbSer, (err) => {
            if (err) {
                console.log(err);
                client.close();
            }
        });
    }
    
    server.on('message', (msg, rinfo) => {
        console.log(JSON.stringify(msg));//获取接收查询 
        forward(msg, rinfo);//转发
    })
    
    server.on('error', (err) => {
        console.log('server error:' + err.stack)
        server.close()
    })
    server.on('listening', () => {
        const addr = server.address()
        console.log(`run ${addr.address}:${addr.port}`)
    })
    server.bind(53);
    

    3.DNS协议请求的报文格式

    从控制台把数组值拷出来

    www.baidu.com
    //十进制数组
    [159, 136, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 119, 119, 119, 5,98, 97, 105, 100, 117, 3, 99, 111, 109, 0, 0, 1, 0, 1]
    

    DNS协议请求的报文简单的可以分为头部正文

    头部 159,136,1,0,0,1,0,0,0,0,0,0
    正文 3,119,119,119,5,98,97,105,100,117,3,99,111,109,0,0,1,0,1

    3.1.头部信息

    3.1.1.会话标识(2字节):

    也就是上面头部中的[159,136],顾名思义就是用来标识是哪个请求的

    3.1.2.标志(2字节):

    也就是上面头部中标识紧接着后面的[1,0]

    对应二进制是0000 0001 0000 0000

    每个二进制值所对应的说明如下:

    标志 说明
    QR(1bit) 查询/响应标志,0为查询,1为响应
    opcode(4bit) 0表示标准查询,1表示反向查询,2表示服务器状态请求
    AA(1bit) 表示授权回答
    TC(1bit) 表示可截断的
    RD(1bit) 表示期望递归
    RA(1bit) 表示可用递归
    ZERO(3bit) 表示保留字段
    rcode(4bit) 表示返回码,0表示没有差错,3表示名字差错,2表示服务器错误(Server Failure)

    3.1.3.数量字段(共8字节):

    头部最后几个字节是[0,1,0,0,0,0,0,0]

    字段 说明
    Questions(2bit)(查询问题数) 表示查询问题区域节的数量,在请求的时候一般为1
    Answer RRs(2bit)(回答RR数) 表示回答区域的数量,根据请求一般为1
    Authority RRs(2bit)(权威RR数) 表示授权区域的数量,一般为0
    Additional RRs(2bit) (附加RR数) 表示附加区域的数量一般为0

    3.2.正文信息

    3.2.1.查询名

    包含域名的可变长度字段,每个域以计数开头,最后一个字符为0。(也会有IP的时候,即反向查询)

    示例:

    3 119 119 119 5 98 97 105 100 117 3 99 111 109 0
    (长度) w w w (长度) b a i d u (长度) c o m (结束)

    3.2.2.查询类型

    查询名之后的[0,1]

    类型 助记符 说明
    1 A 由域名获得IPv4地址,一般是这个
    2 NS 查询域名服务器
    5 CNAME 查询规范名称
    6 SOA 开始授权
    11 WKS 熟知服务
    12 PTR 把IP地址转换成域名
    13 HINFO 主机信息
    15 MX 邮件交换
    28 AAAA 由域名获得IPv6地址
    252 AXFR 传送整个区的请求
    255 ANY 对所有记录的请求

    3.2.3.查询类

    也就是最后的[0,1]
    通常为1,1表示因特网

    4.DNS协议响应的报文格式

    同样从控制台把数组值拷出来

    www.baidu.com
    //十进制数组
    [159, 136,129,128, 0, 1, 0, 1, 0, 0, 0, 0, 3, 119, 119, 119, 5,98, 97, 105, 100, 117, 3, 99, 111, 109, 0, 0, 1, 0, 1, 192, 12, 0, 1, 0, 1, 0, 0, 0, 218, 0, 4, 115, 239, 210, 27]
    

    对比请求响应我们发现,标志部分是[129,128]转为二进制是1000 0001 1000 0000,也就说明
    QRRA标志都是1

    Answer RRs(2bit)(回答RR数)也是1

    当然最明显的是在请求正文最后多了
    [ 192, 12, 0, 1, 0, 1, 0, 0, 0, 218, 0, 4, 115, 239, 210, 27] 我们可以叫他资源记录

    4.1.偏移量

    其中[192,12]是一个(2字节)指针,一般响应报文中,资源部分的地址(域名)一般都是指针C00C(1100000000001100),偏移量是12,指向请求部分的地址(域名)。

    4.2.资源记录的响应类型

    响应类型,也就是后面的[0,1],含义与查询问题部分的类型相同

    4.3.资源记录的响应类

    响应类,也就是后面的[0,1],含义与查询问题部分的类相同

    4.4.生存时间(4字节)

    接下去的是[0, 0, 0, 218],以秒为单位,表示的是资源记录的生命周期,可以理解为获取到的资源记录的缓存时间

    4.5.资源长度

    资源长度是[0, 4],ipv4是00 04

    4.6.资源数据

    资源数据是可变长度的字段,在这里我们拿它来指向IP地址,例如:[115, 239, 210, 27]

    5.编写自定义解析服务

    这里就简单的声明一个hosts对象通过匹配key将匹配到的ip响应到请求端

    const dgram = require('dgram');
    const server = dgram.createSocket('udp4');
    
    const hosts = {//声明host 
        'aaaaaaaa.bbbbbbbbb.com': '127.0.0.1'//自定义
    };
    
    const fbSer= '192.168.1.1';//默认DNS服务器
    function forward(msg, rinfo) {
        const client = dgram.createSocket('udp4');
        client.on('error', (err) => {
            console.log(`client error:` + err.stack)
            client.close();
        })
        client.on('message', (fMsg, fbRinfo) => {
            server.send(fMsg, rinfo.port, rinfo.address, (err) => {
                err && console.log(err);
            });
            client.close();
        })
        client.send(msg, 53, fbSer, (err) => {
            if (err) {
                console.log(err);
                client.close();
            }
        });
    }
    
    function parseHost(msg) {//转换域名
        let num = msg[0];
        let offset = 1;
        let host = "";
        while (num !== 0) {
            host += (msg.slice(offset, offset + num).toString());
            offset += num;
            num = msg[offset];
            offset += 1;
            if (num !== 0) host += ('.');
        }
        return host;
    }
    
    function resolve(ip, msg, rinfo) {//响应
        let len = msg.length;
        let templet = [192, 12, 0, 1, 0, 1, 0, 0, 0, 218, 0, 4].concat(ip.split(".").map(i=>Number(i)));//<===可以自定义
        const response = new ArrayBuffer(len + 16);
        var bufView = new Uint8Array(response);
        for (let i = 0; i < msg.length; i++)bufView[i] = msg[i];
        for (let i = 0; i < templet.length; i++)bufView[msg.length + i] = templet[i];
        bufView[2] = 129;
        bufView[3] = 128;
        bufView[7] = 1; 
        server.send(bufView, rinfo.port, rinfo.address, (err) => {
            if (err) {
                console.log(err);
                server.close();
            }
        })
    }
    
    server.on('message', (msg, rinfo) => {
        let host = parseHost(msg.slice(12));
        let ip = hosts[host];
        if (ip) {
            console.log("resolve:", host, "==>", ip);
            resolve(ip, msg, rinfo); //解析与响应
        } else {
            forward(msg, rinfo);//转发
        }
    })
    
    server.on('error', (err) => {
        console.log('server error:' + err.stack);
        server.close();
    })
    server.on('listening', () => {
        const addr = server.address();
        console.log(`run ${addr.address}:${addr.port}`);
    })
    server.bind(53);
    

    6.Python 代码的实现

    const dgram = require('dgram');
    const server = dgram.createSocket('udp4');
    
    const hosts = {
        'aaaaaaaa.bbbbbbbbb.com': '127.0.0.1', #自定义
    };
    
    const fallbackServer = '192.168.1.1';//默认DNS服务器
    function forward(msg, rinfo) {
        const client = dgram.createSocket('udp4');
        client.on('error', (err) => {
            console.log(`client error:` + err.stack)
            client.close();
        })
        client.on('message', (fMsg, fbRinfo) => {
            server.send(fMsg, 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[0];
        let offset = 1;
        let host = "";
        while (num !== 0) {
            host += (msg.slice(offset, offset + num).toString());
            offset += num;
            num = msg[offset];
            offset += 1;
            if (num !== 0) host += ('.');
        }
        return host;
    }
    
    function resolve(ip, msg, rinfo) {//响应
        let len = msg.length;
        let templet = [192, 12, 0, 1, 0, 1, 0, 0, 0, 218, 0, 4].concat(ip.split(".").map(i=>Number(i)));//<===可以自定义
        const response = new ArrayBuffer(len + 16);
        var bufView = new Uint8Array(response);
        for (let i = 0; i < msg.length; i++)bufView[i] = msg[i];
        for (let i = 0; i < templet.length; i++)bufView[msg.length + i] = templet[i];
        bufView[2] = 129;
        bufView[3] = 128;
        bufView[7] = 1;
        server.send(bufView, rinfo.port, rinfo.address, (err) => {
            if (err) {
                console.log(err);
                server.close();
            }
        })
    }
    
    server.on('message', (msg, rinfo) => {
        let host = parseHost(msg.slice(12));
        let ip = hosts[host];
        if (ip) {
            console.log("resolve:", host, "==>", ip);
            resolve(ip, msg, rinfo); //解析与响应
        } else {
            forward(msg, rinfo);//转发
        }
    })
    
    server.on('error', (err) => {
        console.log('server error:' + err.stack)
        server.close()
    })
    server.on('listening', () => {
        const addr = server.address()
        console.log(`run ${addr.address}:${addr.port}`)
    })
    server.bind(53);
    

    完成

    相关文章

      网友评论

          本文标题:NodeJS编写简单的DNS服务器

          本文链接:https://www.haomeiwen.com/subject/aryggftx.html