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语言中break label与goto label的区别
Apr 28 Golang
完美解决golang go get私有仓库的问题
May 05 Golang
聊聊golang中多个defer的执行顺序
May 08 Golang
go 实现简易端口扫描的示例
May 22 Golang
Golang生成Excel文档的方法步骤
Jun 09 Golang
go开发alertmanger实现钉钉报警
Jul 16 Golang
手把手教你导入Go语言第三方库
Aug 04 Golang
使用GO语言实现Mysql数据库CURD的简单示例
Aug 07 Golang
Golang并发操作中常见的读写锁详析
Aug 30 Golang
简单聊聊Golang中defer预计算参数
Mar 25 Golang
如何解决goland,idea全局搜索快捷键失效问题
Apr 03 Golang
Go中使用gjson来操作JSON数据的实现
Aug 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函数(简单整理)
2010/04/30 PHP
常见的PHP五种设计模式小结
2011/03/23 PHP
PHP数组传递是值传递而非引用传递概念纠正
2013/01/31 PHP
解析如何通过PHP函数获取当前运行的环境 来进行判断执行逻辑(小技巧)
2013/06/25 PHP
php+ajax实现图片文件上传功能实例
2014/06/17 PHP
PHP验证信用卡卡号是否正确函数
2015/05/27 PHP
Riot.js 快速的JavaScript单元测试框架
2009/11/09 Javascript
40个新鲜出炉的jQuery 插件和免费教程[上]
2012/07/24 Javascript
js实现图片轮换效果代码
2013/04/16 Javascript
简介JavaScript中Math.LOG10E属性的使用
2015/06/14 Javascript
JavaScript事件类型中焦点、鼠标和滚轮事件详解
2016/01/25 Javascript
jQuery xml字符串的解析、读取及查找方法
2016/03/01 Javascript
jQuery实现下拉菜单的实例代码
2017/06/19 jQuery
详解ES6 系列之异步处理实战
2018/10/26 Javascript
vue中的适配px2rem示例代码
2018/11/19 Javascript
vue使用自定义指令实现拖拽
2021/01/29 Javascript
Python赋值语句后逗号的作用分析
2015/06/08 Python
Python下Fabric的简单部署方法
2015/07/14 Python
简介Python设计模式中的代理模式与模板方法模式编程
2016/02/02 Python
Python 遍历子文件和所有子文件夹的代码实例
2016/12/21 Python
使用python生成目录树
2018/03/29 Python
解决python3 安装完Pycurl在import pycurl时报错的问题
2018/10/15 Python
python使用opencv在Windows下调用摄像头实现解析
2019/11/26 Python
python 实现在shell窗口中编写print不向屏幕输出
2020/02/19 Python
Python递归求出列表(包括列表中的子列表)的最大值实例
2020/02/27 Python
Python操作MongoDb数据库流程详解
2020/03/05 Python
使用CSS3的ruby-position固定注音位置的用法示例
2016/07/05 HTML / CSS
css3加js做一个简单的3D行星运转效果实例代码
2017/01/18 HTML / CSS
详解如何在css中引入自定义字体(font-face)
2018/05/17 HTML / CSS
玉兰油美国官网:OLAY美国
2018/10/25 全球购物
为什么说Ruby是一种真正的面向对象程序设计语言
2012/10/30 面试题
教师求职自荐信
2014/03/09 职场文书
模范班主任事迹材料
2014/12/17 职场文书
成绩报告单家长评语
2014/12/30 职场文书
人民调解协议书
2016/03/21 职场文书
Win11局域网共享权限在哪里设置? Win11高级共享的设置技巧
2022/04/05 数码科技