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 相关文章推荐
go语言map与string的相互转换的实现
Apr 07 Golang
golang interface判断为空nil的实现代码
Apr 24 Golang
golang中切片copy复制和等号复制的区别介绍
Apr 27 Golang
golang 如何通过反射创建新对象
Apr 28 Golang
解决Golang time.Parse和time.Format的时区问题
Apr 29 Golang
Golang的继承模拟实例
Jun 30 Golang
手把手教你导入Go语言第三方库
Aug 04 Golang
Go中的条件语句Switch示例详解
Aug 23 Golang
Go语言 详解net的tcp服务
Apr 14 Golang
详解Go语言中Get/Post请求测试
Jun 01 Golang
详解Go语言中配置文件使用与日志配置
Jun 01 Golang
Go微服务项目配置文件的定义和读取示例详解
Jun 21 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 破解防盗链图片函数
2008/12/09 PHP
php连接mysql数据库代码
2009/03/10 PHP
php使用Smarty的相关注意事项及访问变量的几种方式
2011/12/08 PHP
Smarty模板引擎缓存机制详解
2016/05/23 PHP
农历与西历对照
2006/09/06 Javascript
jQuery弹出层插件简化版代码下载
2008/10/16 Javascript
javascript web页面刷新的方法收集
2009/07/02 Javascript
关于JavaScript与HTML的交互事件
2013/04/12 Javascript
JS验证日期的格式YYYY-mm-dd 具体实现
2013/06/29 Javascript
jQuery网页右侧广告跟随滚动代码分享
2020/04/20 Javascript
浅析AngularJs HTTP响应拦截器
2015/12/28 Javascript
JS两个数组比较,删除重复值的巧妙方法(推荐)
2016/06/03 Javascript
扩展bootstrap的modal模态框-动态添加modal框-弹出多个modal框
2017/02/21 Javascript
基于ES6 Array.of的用法(实例讲解)
2017/09/05 Javascript
VUE在for循环里面根据内容值动态的加入class值的方法
2018/08/12 Javascript
layui table数据修改的回显方法
2019/09/04 Javascript
Vue实现点击按钮复制文本内容的例子
2019/11/09 Javascript
JS实现判断移动端PC端功能
2020/02/21 Javascript
jQuery实现的上拉刷新功能组件示例
2020/05/01 jQuery
解决VUEX的mapState/...mapState等取值问题
2020/07/24 Javascript
[01:35:53]完美世界DOTA2联赛PWL S3 Magma vs GXR 第二场 12.13
2020/12/17 DOTA
Python对数据进行插值和下采样的方法
2018/07/03 Python
Python利用递归实现文件的复制方法
2018/10/27 Python
python去重,一个由dict组成的list的去重示例
2019/01/21 Python
Django正则URL匹配实现流程解析
2020/11/13 Python
使用CSS3设计地图上的雷达定位提示效果
2016/04/05 HTML / CSS
美国专业汽车音响和移动电子产品零售商:Car Toys
2019/05/13 全球购物
盛大笔试题
2016/11/05 面试题
药剂专业学生求职信范文
2013/12/28 职场文书
班级体育活动总结
2014/07/05 职场文书
高二学年自我鉴定范文(2篇)
2014/09/26 职场文书
群众路线查摆问题整改措施
2014/10/10 职场文书
赢在中国观后感
2015/06/02 职场文书
高考升学宴主持词
2019/06/21 职场文书
goland 恢复已更改文件的操作
2021/04/28 Golang
纯html+css实现Element loading效果
2021/08/02 HTML / CSS