欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

golang DNS服务器的简单实现操作

程序员文章站 2022-06-25 09:18:09
简单的dns服务器提供一个简单的可以查询域名和反向查询的dns服务器。dig命令主要用来从 dns 域名服务器查询主机地址信息。查找www.baidu.com的ip (a记录):命令:dig @127...

简单的dns服务器

提供一个简单的可以查询域名和反向查询的dns服务器。

dig命令主要用来从 dns 域名服务器查询主机地址信息。

查找www.baidu.com的ip (a记录):

命令:dig @127.0.0.1 www.baidu.com

golang DNS服务器的简单实现操作

根据ip查找对应域名 (ptr记录):

命令:dig @127.0.0.1 -x 220.181.38.150

golang DNS服务器的简单实现操作

源码 :

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缓存。