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如何去除多余空白字符(含制表符)
Apr 25 Golang
golang json数组拼接的实例
Apr 28 Golang
彻底理解golang中什么是nil
Apr 29 Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 Golang
Golang: 内建容器的用法
May 05 Golang
golang 实现时间戳和时间的转化
May 07 Golang
Golang标准库syscall详解(什么是系统调用)
May 25 Golang
go goroutine 怎样进行错误处理
Jul 16 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
Golang MatrixOne使用介绍和汇编语法
Apr 19 Golang
Golang获取List列表元素的四种方式
Apr 20 Golang
详解Go语言中Get/Post请求测试
Jun 01 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
绿山咖啡和蓝山咖啡
2021/03/04 新手入门
PHP输出控制功能在简繁体转换中的应用
2006/10/09 PHP
thinkphp的CURD和查询方式介绍
2013/12/19 PHP
PHP时间类完整实例(非常实用)
2015/12/25 PHP
Laravel中日期时间处理包Carbon的简单使用
2017/09/21 PHP
Thinkphp5 自定义上传文件名的实现方法
2019/07/23 PHP
IE7中javascript操作CheckBox的checked=true不打勾的解决方法
2009/12/07 Javascript
JavaScript自定义数组排序方法
2015/02/12 Javascript
js立即执行函数: (function ( ){})( ) 与 (function ( ){}( )) 有什么区别?
2015/11/18 Javascript
node.js利用redis数据库缓存数据的方法
2017/03/01 Javascript
AngularJS动态绑定ng-options的ng-model实例代码
2017/06/21 Javascript
浅谈Vuejs Prop基本用法
2017/08/17 Javascript
详解ES7 Decorator 入门解析
2019/02/18 Javascript
JavaScript碎片—函数闭包(模拟面向对象)
2019/03/13 Javascript
jquery绑定事件 bind和on的用法与区别分析
2020/05/22 jQuery
解决vue单页面 回退页面 keeplive 缓存问题
2020/07/22 Javascript
VUE Elemen-ui之穿梭框使用方法详解
2021/01/19 Javascript
用vue设计一个日历表
2020/12/03 Vue.js
[02:57]DOTA2英雄基础教程 风行者
2014/01/16 DOTA
[49:20]2014 DOTA2国际邀请赛中国区预选赛5.21 CIS VS TongFu
2014/05/22 DOTA
django项目运行因中文而乱码报错的几种情况解决
2017/11/07 Python
Python装饰器用法实例分析
2019/01/14 Python
Python PyCharm如何进行断点调试
2019/07/05 Python
简单了解python的break、continue、pass
2019/07/08 Python
扩展Django admin的list_filter()可使用范围方法
2019/08/21 Python
Python新手如何进行闭包时绑定变量操作
2020/05/29 Python
详解如何将 Canvas 绘制过程转为视频
2021/01/25 HTML / CSS
美国波西米亚风格服装品牌:Show Me Your Mumu
2018/01/05 全球购物
大二自我鉴定范文
2013/10/05 职场文书
企业优秀团员事迹材料
2014/08/20 职场文书
财务会计岗位职责
2015/02/03 职场文书
2015年乡镇科普工作总结
2015/05/13 职场文书
幼儿园教师心得体会范文
2016/01/21 职场文书
详解MySQL多版本并发控制机制(MVCC)源码
2021/06/23 MySQL
MyBatis核心源码深度剖析SQL语句执行过程
2022/05/20 Java/Android
MySQL 原理与优化之Update 优化
2022/08/14 MySQL