golang DNS服务器的简单实现操作
简单的dns服务器
提供一个简单的可以查询域名和反向查询的dns服务器。
dig命令主要用来从 dns 域名服务器查询主机地址信息。
查找www.baidu.com的ip (a记录):
命令:dig @127.0.0.1 www.baidu.com
根据ip查找对应域名 (ptr记录):
命令:dig @127.0.0.1 -x 220.181.38.150
源码 :
package mainimport ( "fmt" "net" "golang.org/x/net/dns/dnsmessage")func main() { conn, err := net.listenudp("udp", &net.udpaddr{port: 53}) if err != nil { panic(err) } defer conn.close() fmt.println("listing ...") for { buf := make([]byte, 512) _, addr, _ := conn.readfromudp(buf) var msg dnsmessage.message if err := msg.unpack(buf); err != nil { fmt.println(err) continue } go serverdns(addr, conn, msg) }}// address booksvar ( addressbookofa = map[string][4]byte{ "www.baidu.com.": [4]byte{220, 181, 38, 150}, } addressbookofptr = map[string]string{ "150.38.181.220.in-addr.arpa.": "www.baidu.com.", })// serverdns servefunc serverdns(addr *net.udpaddr, conn *net.udpconn, msg dnsmessage.message) { // query info if len(msg.questions) < 1 { return } question := msg.questions[0] var ( querytypestr = question.type.string() querynamestr = question.name.string() querytype = question.type queryname, _ = dnsmessage.newname(querynamestr) ) fmt.printf("[%s] queryname: [%s]\n", querytypestr, querynamestr) // find record var resource dnsmessage.resource switch querytype { case dnsmessage.typea: if rst, ok := addressbookofa[querynamestr]; ok { resource = newaresource(queryname, rst) } else { fmt.printf("not fount a record queryname: [%s] \n", querynamestr) response(addr, conn, msg) return } case dnsmessage.typeptr: if rst, ok := addressbookofptr[queryname.string()]; ok { resource = newptrresource(queryname, rst) } else { fmt.printf("not fount ptr record queryname: [%s] \n", querynamestr) response(addr, conn, msg) return } default: fmt.printf("not support dns querytype: [%s] \n", querytypestr) return } // send response msg.response = true msg.answers = append(msg.answers, resource) response(addr, conn, msg)}// response returnfunc response(addr *net.udpaddr, conn *net.udpconn, msg dnsmessage.message) { packed, err := msg.pack() if err != nil { fmt.println(err) return } if _, err := conn.writetoudp(packed, addr); err != nil { fmt.println(err) }}// newaresource a recordfunc newaresource(query dnsmessage.name, a [4]byte) dnsmessage.resource { return dnsmessage.resource{ header: dnsmessage.resourceheader{ name: query, class: dnsmessage.classinet, ttl: 600, }, body: &dnsmessage.aresource{ a: a, }, }}// newptrresource ptr recordfunc newptrresource(query dnsmessage.name, ptr string) dnsmessage.resource { name, _ := dnsmessage.newname(ptr) return dnsmessage.resource{ header: dnsmessage.resourceheader{ name: query, class: dnsmessage.classinet, }, body: &dnsmessage.ptrresource{ ptr: name, }, }}
补充:golang自定义dns nameserver
某些情况下我们希望程序通过自定义nameserver去查询域名,而不希望通过操作系统给定的nameserver,本文介绍如何在golang中实现自定义nameserver。
dns解析过程
golang中一般通过net.resolver的lookuphost(ctx context.context, host string) (addrs []string, err error)去实现域名解析,
解析过程如下:
检查本地hosts文件是否存在解析记录,存在即返回解析地址
不存在即根据resolv.conf中读取的nameserver发起递归查询
nameserver不断的向上级nameserver发起迭代查询
nameserver最终返回查询结果给请求者
用户可以通过修改/etc/resolv.conf来添加特定的nameserver,但某些场景下我们不希望更改系统配置。比如在kubernetes中,作为sidecar服务需要通过service去访问其他集群内服务,必须更改dnspolicy为clusterfirst,但这可能会影响其他容器的dns查询效率。
自定义nameserver
在golang中自定义nameserver,需要我们自己实现一个resolver,如果是httpclient需要自定义dialcontext()
resolver实现如下:
// 默认dialerdialer := &net.dialer{ timeout: 1 * time.second,}// 定义resolverresolver := &net.resolver{ dial: func(ctx context.context, network, address string) (net.conn, error) { return dialer.dialcontext(ctx, "tcp", nameserver) // 通过tcp请求nameserver解析域名 },}
自定义dialer如下:
type dialer struct { dialer *net.dialer resolver *net.resolver nameserver string}// newdialer create a dialer with user's nameserver.func newdialer(dialer *net.dialer, nameserver string) (*dialer, error) { conn, err := dialer.dial("tcp", nameserver) if err != nil { return nil, err } defer conn.close() return &dialer{ dialer: dialer, resolver: &net.resolver{ dial: func(ctx context.context, network, address string) (net.conn, error) { return dialer.dialcontext(ctx, "tcp", nameserver) }, }, nameserver: nameserver, // 用户设置的nameserver }, nil}// dialcontext connects to the address on the named network using// the provided context.func (d *dialer) dialcontext(ctx context.context, network, address string) (net.conn, error) { host, port, err := net.splithostport(address) if err != nil { return nil, err } ips, err := d.resolver.lookuphost(ctx, host) // 通过自定义nameserver查询域名 for _, ip := range ips { // 创建链接 conn, err := d.dialer.dialcontext(ctx, network, ip+":"+port) if err == nil { return conn, nil } } return d.dialer.dialcontext(ctx, network, address)}
httpclient中自定义dialcontext()如下:
ndialer, _ := newdialer(dialer, nameserver)client := &http.client{ transport: &http.transport{ dialcontext: ndialer.dialcontext, tlshandshaketimeout: 10 * time.second, }, timeout: timeout,}
总结
通过以上实现可解决自定义nameserver,也可以在dailer中添加缓存,实现dns缓存。
上一篇: 我没爆他菊花
下一篇: vue前端开发层次嵌套组件的通信详解