Python利用socket架设DNS服务

DNS报文

DNS数据包包含HEADER,QUESTION,ANSWER,Authority,Additional几个部分,但并非每个部分都是必须的,比如发送一个查询请求可能只包含HEADER与QUESTION部分,而返回查询结果可能只需要HEADER,QUESTION,ANSWER即可。本文架设一个简单的DNS服务也只需要用到这三块。

 

dns-sniffer

数据包分析

请求数据包

dns-query

上图便是一个标准的DNS查询请求,简单说明一下。

属于DNS数据包的位:

 

f5 82 01 00 00 01 00 00 00 00 00 00 08 7a 68 75 6a 75 6e 77 75 02 63 6e 00 00 01 00 01

HEADER部分占12字节,对应”f5 82 01 00 00 01 00 00 00 00 00 00

ID:客户端随机生成的标志符,对应”f582“,占16位即2字节;

QR~RCode总共占16位2字节,对应0100

QR:0-请求,1-应答,占1位;

Opcode:0000-标准查询,0001-反向查询,0010-服务器状态查询,占4位;

RD:1-使用递归,占1位;

Z:保留位,占3位;

RCode:状态,0-无误,占4位;

下述几个Count占8个字节,对应”0001000000000000

QDCount:请求记录数量,占16位;

ANCount:应答记录数量,占16位;

NSCount:授权记录数量,占16位;

ARCount:附加记录数量,占16位;

QUESTION部分

QName:目标域名,长度不固定;

“08 7a 68 75 6a 75 6e 77 75 02 63 6e”,第一个字节”08″表示域名第一部分长度为8,那么接下来的8个字节将对应”zhujunwu”,倒数第三个字节”02″表示第二部分长度为2,那么接下来2个字节对应”cn”,如果域名仍未结束,则重复上述操作;

QType:查询类型,其中”1″表示A记录,占16位;

QClass:查询分类,其中”1″表示INTENET,占16位;

应答数据包

dns-response

QR为1表示应答,RD为1表示期望递归,RA为1表示使用递归,所以QR~RCode组合成”8180″;

应答的QUESTION与请求的QUESTION部分一致;

ANSWER部分开始,C0表示指向字段,0C表示从头偏移12个字节,即指向QName地址,拒绝代码重复;

ANSWER中NAME,TYPE,CLASS与QUESTION格式一致;

TTL:生存时间,占64位即4个字节;

RDLength:响应资源长度,占15位,比如这里为”4″;

RDdata:那么这里的4个字节将表示响应资源;

Python服务端

 

import socket, sys
 
class DNSQuery:
    def __init__(self, data):
        self.data=data
        self.domain=''
 
        opcode = (ord(data[2]) >> 3) & 15   # ord(data[2]包含QR(1),Opcode(4),AA(1),TC(1),RD(1);位移+位与将获取Opcode
        if opcode == 0:
            qindex=12  # QUESTION部分开始位置
            qlength=ord(data[qindex])   # 数据长度,zhujunwu.cn: data[12]=len('zhujunwu')=>8,data[21]=len('cn')=>2
            while qlength != 0: # 获取域名
                self.domain+=data[qindex+1:qindex+qlength+1]+'.'
                qindex+=qlength+1
                qlength=ord(data[qindex])
 
    def response(self, ip):
        packet=''
        if self.domain:
            packet+=self.data[:2] + "x81x80"  # id + QR=1(应答), RD=1(期望递归), RA=1(支持递归)
            packet+=self.data[4:6] + self.data[4:6] + 'x00x00x00x00'    # QDCount(16) + ANCount(16) + NSCount(16) + ARCount(16)
            packet+=self.data[12:]  # QUESTION部分
            packet+='xc0x0c'  # ANSWER部分开始,C0表示指向字段,0C表示从头偏移12个字节,即指向QName地址,拒绝代码重复
            packet+='x00x01x00x01x00x00x00x3cx00x04'  # RType=0001, RClass=0001, TTL=0000003c, RDLength=0004
            packet+=str.join('',map(lambda x: chr(int(x)), ip.split('.')))  # RDdata, IP占4字节
        return packet
 
if __name__ == '__main__':
    # ip = '192.168.1.10' # 放服务器上的话,配置这儿即可
    while 1:
        try:
            ip = raw_input('Target IP: ')
        except:
            sys.exit(0)
        try:
            socket.inet_aton(ip)
            break
        except:
            print 'Unavailable ip address, please try again!'
   
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(('',53))

    try:
        while 1:
            data, addr = s.recvfrom(1024)
            p=DNSQuery(data)
            s.sendto(p.response(ip), addr)
            print 'Respuesta: %s -> %s' % (p.domain, ip)
    except KeyboardInterrupt:
        s.close()
        sys.exit(0)