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


Posted in Golang onApril 30, 2021

简单的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缓存。

Golang 相关文章推荐
golang判断key是否在map中的代码
Apr 24 Golang
解决Golang中ResponseWriter的一个坑
Apr 27 Golang
golang 实现对Map进行键值自定义排序
Apr 28 Golang
对Golang中的FORM相关字段理解
May 02 Golang
golang 比较浮点数的大小方式
May 02 Golang
golang日志包logger的用法详解
May 05 Golang
golang 实现时间戳和时间的转化
May 07 Golang
关于golang高并发的实现与注意事项说明
May 08 Golang
浅谈Golang 切片(slice)扩容机制的原理
Jun 09 Golang
golang实现浏览器导出excel文件功能
Mar 25 Golang
golang生成并解析JSON
Apr 14 Golang
golang语言指针操作
Apr 14 Golang
golang slice元素去重操作
Apr 30 #Golang
Golang中interface{}转为数组的操作
Apr 30 #Golang
解决Go gorm踩过的坑
Apr 30 #Golang
Golang 如何实现函数的任意类型传参
Apr 29 #Golang
解决Golang time.Parse和time.Format的时区问题
Apr 29 #Golang
Golang 使用Map实现去重与set的功能操作
Apr 29 #Golang
使用Golang的channel交叉打印两个数组的操作
Apr 29 #Golang
You might like
纯PHP生成的一个树叶图片画图例子
2014/04/16 PHP
php获取本周开始日期和结束日期的方法
2015/03/09 PHP
php的crc32函数使用时需要注意的问题(不然就是坑)
2015/04/21 PHP
PHP的Laravel框架中使用消息队列queue及异步队列的方法
2016/03/21 PHP
24条货真价实的PHP代码优化技巧
2016/07/28 PHP
JavaScript 选中文字并响应获取的实现代码
2011/08/28 Javascript
js中生成map对象的方法
2014/01/09 Javascript
JavaScript设计模式之装饰者模式介绍
2014/12/28 Javascript
jquery实现可点击伸缩与展开的菜单效果代码
2015/08/31 Javascript
基于javascript编写简单日历
2016/05/02 Javascript
浅谈js继承的实现及公有、私有、静态方法的书写
2016/10/28 Javascript
Angular.Js中过滤器filter与自定义过滤器filter实例详解
2017/05/08 Javascript
vue实现登陆登出的实现示例
2017/09/15 Javascript
vue2.0 axios跨域并渲染的问题解决方法
2018/03/08 Javascript
Vue实现导航栏的显示开关控制
2019/11/01 Javascript
js中关于Blob对象的介绍与使用
2019/11/29 Javascript
[49:17]DOTA2-DPC中国联赛 正赛 Phoenix vs Dynasty BO3 第三场 1月26日
2021/03/11 DOTA
Python获取暗黑破坏神3战网前1000命位玩家的英雄技能统计
2016/07/04 Python
基于python爬虫数据处理(详解)
2017/06/10 Python
Django文件存储 默认存储系统解析
2019/08/02 Python
python3 实现的对象与json相互转换操作示例
2019/08/17 Python
python cv2截取不规则区域图片实例
2019/12/21 Python
PyChon中关于Jekins的详细安装(推荐)
2020/12/28 Python
浅析HTML5中header标签的用法
2016/06/24 HTML / CSS
Lookfantastic日本官网:英国知名护肤、化妆品和头发护理购物网站
2018/04/21 全球购物
工商管理系学生的自我评价分享
2013/11/29 职场文书
表彰先进的通报
2014/01/31 职场文书
财务管理职业生涯规划书
2014/02/26 职场文书
元旦联欢会主持词
2014/03/26 职场文书
新学期国旗下演讲稿
2014/05/08 职场文书
党的群众路线教育实践活动个人对照检查材料(企业)
2014/11/05 职场文书
2015年爱国卫生工作总结
2015/04/22 职场文书
jQuery class属性操作addClass()与removeClass()、hasClass()、toggleClass()
2021/03/31 jQuery
Mysql超详细讲解死锁问题的理解
2022/04/01 MySQL
vue实现列表拖拽排序的示例代码
2022/04/08 Vue.js
CSS SandBox应用场景及常见问题
2022/06/25 HTML / CSS