这个DNS系列现在有以下几篇文章
DNS EDNS DNSSEC DNS over HTTPS 完整代码请看DNS
DNS Header
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| ID |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+|QR| Opcode |AA|TC|RD|RA| Z | RCODE |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| QDCOUNT |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| ANCOUNT |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| NSCOUNT |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| ARCOUNT |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
header在dns请求和应答中相同的,查询中有Query Section,应答中有answer section.
- ID: 2字节,应答中也有ID,可以用来判断是否为我们请求的应答
- QR:1bit,请求时为0,应答时为1
- Opcode:4bit 通常值为0(标准查询),其他值为1(反向查询)和2(服务器状态请求),[3,15]保留值
- AA:1bit authoritative answer,在应答中才有效
- TC:1bit 表示可截断
- RD:1bit 期望使用递归查询
- RA:1bit 在应答中返回,返回服务器是否支持递归查询
- Z: Reserved for future use.
- RCODE:4bit,应答码,代表返回的状态
- 0 No Error
- 1 Format error 格式错误
- 2 Server failure 服务器失败
- 3 Name Error 查询域名错误
- 4 Not Implemented 未实现的查询方式
- 5 Refused 拒绝
- 6-15 Reserved for future use.
- QDCOUNT 请求的个数
- ANCOUNT 应答的个数
- NSCOUNT 无符号16bit整数表示报文授权段中的授权记录数。
- ARCOUNT 无符号16bit整数表示报文附加段中的附加记录数。
查询
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| |/ QNAME // /+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| QTYPE |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+| QCLASS |+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
QNAME:不定长,格式为域名以点分割的长度,末尾以0结尾,例如:
www.google.com -> 3www6google3com0 -
QTYPE:查询的类型
常用的我们需要知道A为IPV4,AAAA为IPV6TYPE value and meaningA 1 a host addressNS 2 an authoritative name serverMD 3 a mail destination (Obsolete - use MX)MF 4 a mail forwarder (Obsolete - use MX)CNAME 5 the canonical name for an aliasSOA 6 marks the start of a zone of authorityMB 7 a mailbox domain name (EXPERIMENTAL)MG 8 a mail group member (EXPERIMENTAL)MR 9 a mail rename domain name (EXPERIMENTAL)NULL 10 a null RR (EXPERIMENTAL)WKS 11 a well known service descriptionPTR 12 a domain name pointerHINFO 13 host informationMINFO 14 mailbox or mail list informationMX 15 mail exchangeTXT 16 text strings AAAA 28 <- https://www.ietf.org/rfc/rfc3596.txt -
QCLASS:无符号16bit整数表示查询的类,比如,IN代表Internet.
现在我们来完成生成请求的代码
使用全局变量来存储请求的类型
type reqType [2]byte var ( A = reqType{0b00000000, 0b00000001} // 1 NS = reqType{0b00000000, 0b00000010} // 2 MD = reqType{0b00000000, 0b00000011} // 3 MF = reqType{0b00000000, 0b00000100} // 3 CNAME = reqType{0b00000000, 0b00000101} // 5 SOA = reqType{0b00000000, 0b00000110} // 6 MB = reqType{0b00000000, 0b00000111} // 7 MG = reqType{0b00000000, 0b00001000} // 8 MR = reqType{0b00000000, 0b00001001} // 9 NULL = reqType{0b00000000, 0b00001010} // 10 WKS = reqType{0b00000000, 0b00001011} // 11 PTR = reqType{0b00000000, 0b00001100} // 12 HINFO = reqType{0b00000000, 0b00001101} // 13 MINFO = reqType{0b00000000, 0b00001110} // 14 MX = reqType{0b00000000, 0b00001111} // 15 TXT = reqType{0b00000000, 0b00010000} // 16 AAAA = reqType{0b00000000, 0b00011100} // 28 https://www.ietf.org/rfc/rfc3596.txt // for req AXFR = reqType{0b00000000, 0b11111100} // 252 ANY = reqType{0b00000000, 0b11111111} // 255)
这里需要注意有些字段是以位的大小来存储的,需要使用移位运算符来计算(在完成这个协议前,我写了socks5,因为socks5中全部为字节的大小,可能有人会出现与我相同的错误,所以需要注意一下)
func creatRequest(domain string, reqType reqType) []byte { id := []byte{byte(rand.Intn(255)), byte(rand.Intn(255))} // id: qr := byte(0b0) // qr 0 opCode := byte(0b0000) // opcode 0000 aa := byte(0b0) // aa 0 tc := byte(0b0) // tc 0 rd := byte(0b1) // rd 1 ra := byte(0b0) // ra 0 z := byte(0b000) // z 000 rCode := byte(0b0000) // rCode 0000 qr2rCode := []byte{qr<<7 + opCode<<3 + aa<<2 + tc<<1 + rd, ra<<7 + z<<4 + rCode} qdCount := []byte{0b00000000, 0b00000001} // request number => bit: 00000000 00000001 -> 01 anCount := []byte{0b00000000, 0b00000000} // answer number(no use for req) => bit: 00000000 00000000 nsCount := []byte{0b00000000, 0b00000000} //(no use for req) => bit: 00000000 00000000 arCount := []byte{0b00000000, 0b00000000} //(no use for req) => bit: 00000000 00000000 var qName []byte for _, x := range strings.Split(domain, ".") { qName = append(qName, byte(len(x))) qName = append(qName, []byte(x)...) } qName = append(qName, 0b00000000) // add the 0 for last of domain qType := []byte{reqType[0], reqType[1]} // type: qClass := []byte{0b00000000, 0b00000001} // 1 -> from internet return bytes.Join([][]byte{id, qr2rCode, qdCount, anCount, nsCount, arCount, qName, qType, qClass}, []byte{})}
这里我们已经得到了完整的请求字节数组,然后使用UDP协议发起请求就行了:
// 此处以www.google.com及1111 DNS为例req := creatRequest("www.google.com", A)conn, err := net.DialTimeout("udp", "1.1.1.1:53", 5*time.Second)if err != nil { return nil, err}defer conn.Close()if _, err = conn.Write(req); err != nil { return nil, err}_ = conn.SetDeadline(time.Now().Add(5 * time.Second))n, err := conn.Read(b[:])if err != nil { return nil, err}
接下来我们就需要解析应答请求了,来获取我们需要的数据.
应答
应答数据中具有header和请求字段,所以我们可以先写一个resovle header来分析header.
之前我们完成了域名编码的代码,就是3www6google3com0这个,这里我们还需要完成一个解析的.
func getName(c []byte, all []byte) (name string, x []byte) { for { if c[0]&128 == 128 && c[0]&64 == 64 { // <- 这里的这个会在下面的answer section中解释 l := c[1] c = c[2:] tmp, _ := getName(all[l:], all) name += tmp //log.Println(c, name) break } name += string(c[1:int(c[0])+1]) + "." c = c[int(c[0])+1:] if c[0] == 0 { c = c[1:] // lastOfDomain: one byte 0 break } } return name, c}
之后就可以来分析header了:
func resolveHeader(req []byte, answer []byte) (anCount int, answerSection []byte, err error) { // resolve answer if answer[0] != req[0] || answer[1] != req[1] { // compare id // not the answer return 0, nil, errors.New("id not same") } if answer[2]&8 != 0 { // check the QR is 1(Answer) return 0, nil, errors.New("the qr is not 1(Answer)") } rCode := fmt.Sprintf("%08b", answer[3])[4:] // check Response code(rCode) switch rCode { case "0000": // no error break case "0001": // Format error return 0, nil, errors.New("request format error") case "0010": //Server failure return 0, nil, errors.New("dns Server failure") case "0011": //Name Error return 0, nil, errors.New("no such name") case "0100": // Not Implemented return 0, nil, errors.New("dns server not support this request") case "0101": //Refused return 0, nil, errors.New("dns server Refuse") default: // Reserved for future use. return 0, nil, errors.New("other error") } //qdCountA := []byte{b[4], b[5]} // no use, for request //anCountA := []byte{answer[6], answer[7]} anCount = int(answer[6])<<8 + int(answer[7]) //nsCount2arCountA := []byte{b[8], b[9], b[10], b[11]} // no use c := answer[12:] var x string x, c = getName(c, answer) log.Println(x) log.Println("qType:", c[:2]) c = c[2:] log.Println("qClass:", c[:2]) c = c[2:] return anCount, c, nil}
之后就是我们真正需要的数据,Answer section
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 // /+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
- NAME:不定长与之前QNAME相同,这里会使用省略字段:前两bit为11表示压缩格式,而后面跟的14bit表示的是Name所在的位置相对于DNS首部的偏移值
如:之前的header数组中出现过3www6google3com0,且处的位置为12,这里就可以用192 12两个字节来省略(192是因为11000000,前两位为11) - QTYPE:与之前的TYPE相同
- CLASS:与之前的QCLASS相同
- TTL: 就是TTL 可以使用搜索引擎查询一下
- RDLENGTH: RDATA的长度
这个Answer section与之前的header不同,并不是只出现一次,与header中ANCOUNT有关,如果ANCOUNT不为1,后面就会出现多个,要使用循环来获取所有应答数据
// resolve answeranCount, c, err := resolveHeader(req, b[:n])if err != nil { return nil, err} // answer sectionlog.Println()log.Println("Answer section:") var x stringfor anCount != 0 { x, c = getName(c, b[:n]) log.Println(x) tYPE := reqType{c[0], c[1]} log.Println("type:", c[0], c[1]) c = c[2:] // type log.Println("class:", c[0], c[1]) c = c[2:] // class log.Println("ttl:", c[0], c[1], c[2], c[3]) c = c[4:] // ttl 4byte sum := int(c[0])<<8 + int(c[1]) log.Println("rdlength", sum) c = c[2:] // RDLENGTH 跳过总和,因为总和不包括计算域名的长度 2+int(c[0])<<8+int(c[1]) switch tYPE { case A: DNS = append(DNS, c[0:4]) c = c[4:] // 4 byte ip addr case AAAA: DNS = append(DNS, c[0:16]) c = c[16:] // 16 byte ip addr case NS: fallthrough case MD: fallthrough case MF: fallthrough case CNAME: fallthrough case SOA: fallthrough case MG: fallthrough case MB: fallthrough case MR: fallthrough case NULL: fallthrough case WKS: fallthrough case PTR: fallthrough case HINFO: fallthrough case MINFO: fallthrough case MX: fallthrough case TXT: fallthrough default: log.Println("rdata", c[:sum]) c = c[sum:] // RDATA } anCount -= 1}
虽然这里我只处理了A,和AAAA的请求类型,但是每个类型的数据格式在rfc1035都有详细记录,想要自己实现并不难,毕竟我们已经完成的大部分的数据解析,剩下的一点也应该问题不大.
正在加载评论...