###file QueryDNS.py##
-*- coding: utf-8 -*-
#Get DNS answer
#详情见RFC 1035
import os, sys
import socket
import struct
import random
from domaintobyte import domaintobyte, bytetodomainDHOST = '208.67.222.222' #DNS
服务器的地址
DPORT = 53 #默认端口是53LHOST = ''
LPORT = 10001#绑定到的本地端口TIMEOUT = 3.0#超时设置为3秒##数据包整体的格式
## +---------------------+
## | Header |
## +---------------------+
## | Question | the question for the name server
## +---------------------+
## | Answer | RRs answering the question
## +---------------------+
## | Authority | RRs pointing toward an authority
## +---------------------+
## | Additional | RRs holding additional information
## +---------------------+def QueryDNS(domain):
TID = random.randint(-32768, 32767)
Flags = 0x0100
Questions = 0x0001
AnswerRRs = 0x0000
AuthorityRRs = 0x0000
AdditionalRRs = 0x0000 TIDCHARS = struct.pack('!h', TID) domainbyte = domaintobyte(domain)
#TYPE value and meaning
#A 1 a host address
#NS 2 an authoritative name server
#MD 3 a mail destination (Obsolete - use MX)
#MF 4 a mail forwarder (Obsolete - use MX)
#CNAME5 the canonical name for an
alias
SEARCHTYPE = 0x0001
#Class 一般为 1 指 Internet
SEARCHCLASS = 0x0001 #构造请求报文
Bufhead = struct.pack('!hhhhhh', TID, Flags, Questions, AnswerRRs, AuthorityRRs, AdditionalRRs)
Buftail = struct.pack('!hh', SEARCHTYPE, SEARCHCLASS)
Buf = Bufhead + domainbyte + Buftail s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(TIMEOUT) #设置超时时间
s.bind((LHOST,LPORT))
s.sendto(Buf,(DHOST, DPORT))
print 'Send [OK]'
try:
data, addr = s.recvfrom(1024)
except socket.timeout:
s.close()
print u'响应超时'
return
s.close()
print 'From', addr
#print 'Receved', repr(data)
if data[0:2] == TIDCHARS:
#Header 部分
#[0:2]数据包最开始两个字节是TID
print 'TID [0k]'
#[2:4]接着是Flags 两个字节
#Flags 的最后4位是错误代码,如果是0000则表示没有错误,否则就有错误
flags = struct.unpack('!h', data[2:4])[0]
errormsg = flags & 0x000F
if errormsg != 0:
print "Error, maybe no such domain"
return
#[4:6]Questions 数 两个字节
#[6:8]Answer RRs数 两个字节
#[8:10]Authority RRs数 两个字节
#[10:12]Additional RRs数 两个字节
answerRRs = struct.unpack('!h', data[6:8])[0] bitflags = 12; #跳过上面12个字节
#Question 部分
#Name:
域名-不定长度,以' '结尾
#Type: 查询类型 两个字节
#Class:一般为1(Internet) 两个字节
while data[bitflags] != 'x00':
bitflags += 1
bitflags += 1 #域名结束 bitflags += 2 #跳过Type
bitflags += 2 #跳过Class
print 'Answers', answerRRs
i = 0#Answer 部分一条记录的格式
#
# 1 1 1 1 1 1
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
# | |
# / /
# /NAME /
# | |
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
# |TYPE |
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
# | CLASS |
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
# |TTL|
# | |
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
# | RDLENGTH |
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
# / RDATA /
# / /
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ while i < answerRRs:
#Name 两个字节 指针-指向域名的字符
bitflags += 2
if data[bitflags:bitflags+2] == 'x00x05':#CNAME
#Type 两个字节
#Class两个字节
#TTL 四个字节
bitflags += 8
rdatalength = struct.unpack('!h', data[bitflags:bitflags+2])[0]
#Data length 两个字节
bitflags += 2
#得到rdata中的全部域名
fullRecord = GetFullName(bitflags, data)
bitflags += rdatalength
print 'CNAME:', bytetodomain(fullRecord)
elif data[bitflags:bitflags+2] == 'x00x01':#A(Host Address)
#Type 两个字节
#Class两个字节
#TTL 四个字节
bitflags += 8
rdatalength = struct.unpack('!h', data[bitflags:bitflags+2])[0]
#Data length 两个字节
bitflags += 2
#A记录的RDatalength = 4
iptuple = struct.unpack('!BBBB', data[bitflags:bitflags+4])
ipstr = '%d.%d.%d.%d' % iptuple
bitflags += rdatalength
print 'IP:', ipstr i += 1 #CNAME记录中域名并不是全部存储在rdata中,和Query或者其他包含域名部分有重复的部分是用一个指针,指向数据包的一个地址,这样就节省了存储空间
#offset是相对数据包头部的偏移量
#两个字节 16bit如下图所示
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
# | 1 1| OFFSET |
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#比如
#Question部分中的查询的是www.sina.com.cn
#CNAME记录中的域名是jupiter.sina.com.cn,只用在rdata中存储"jupiter",接下来的部分用一个指针指向Question部分中的"sina.com.cn"
#CNAME中的rdata
# +--+--+--+--+--+--+--+--+--+--+
# |07| j| u| p| i| t| e| r|c0|10|
# +--+--+--+--+--+--+--+--+--+--+
#最后两个字节就是一个指针,指向0x10这个地址
def GetFullName(offset, Buf):
fullRecord = ''
oneChar = struct.unpack('!B', Buf[offset:offset+1])[0]
#print oneChar
if oneChar & 0xc0 == 0xc0 : #指针
jump = struct.unpack('!h', Buf[offset:offset+2])[0]
jump = jump & 0x3FFF #指针指向的地址
fullRecord += GetFullName(jump, Buf)
elif oneChar == 0 : #域名以 结束
return 'x00'
else :#域名部分
fullRecord += Buf[offset:offset+oneChar+1]
fullRecord += GetFullName(offset+oneChar+1, Buf)
return fullRecord
if __name__ == "__main__":
if len(sys.argv) < 2:
print "Usage:QueryDNS.py www.google.cn"
else:
domain = sys.argv[1]
QueryDNS(domain)