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中ResponseWriter的一个坑
Apr 27 Golang
golang 在windows中设置环境变量的操作
Apr 29 Golang
解决Golang time.Parse和time.Format的时区问题
Apr 29 Golang
golang 比较浮点数的大小方式
May 02 Golang
Golang全局变量加锁的问题解决
May 08 Golang
入门学习Go的基本语法
Jul 07 Golang
Golang中channel的原理解读(推荐)
Oct 16 Golang
Go语言实现一个简单的并发聊天室的项目实战
Mar 18 Golang
golang操作redis的客户端包有多个比如redigo、go-redis
Apr 14 Golang
golang的文件创建及读写操作
Apr 14 Golang
Golang bufio详细讲解
Apr 21 Golang
Golang gRPC HTTP协议转换示例
Jun 16 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 Yii框架之表单验证规则大全
2015/11/16 PHP
PHP内核学习教程之php opcode内核实现
2016/01/27 PHP
PHP PDOStatement::nextRowset讲解
2019/02/01 PHP
laravel利用中间件做防非法登录和权限控制示例
2019/10/21 PHP
一个JavaScript继承的实现
2006/10/24 Javascript
IE8 原生JSON支持
2009/04/13 Javascript
juqery 学习之四 筛选查找
2010/11/30 Javascript
前台js对象在后台转化java对象的问题探讨
2013/12/20 Javascript
JavaScript生成随机数的4种自定义函数分享
2015/02/28 Javascript
JavaScript中的getTimezoneOffset()方法使用详解
2015/06/10 Javascript
EasyUi combotree 实现动态加载树节点
2016/04/01 Javascript
Angular2 组件间通过@Input @Output通讯示例
2017/08/24 Javascript
vue2.0 + ele的循环表单及验证字段方法
2018/09/18 Javascript
vue elementUI 表单校验的实现代码(多层嵌套)
2019/11/06 Javascript
[02:28]DOTA2亚洲邀请赛 LGD战队巡礼
2015/02/03 DOTA
[00:17]天涯墨客一技能展示
2018/08/25 DOTA
Python实现的排列组合计算操作示例
2017/10/13 Python
Python Learning 列表的更多操作及示例代码
2018/08/22 Python
使用Python开发SQLite代理服务器的方法
2018/12/07 Python
python列表推导和生成器表达式知识点总结
2020/01/10 Python
Python文件时间操作步骤代码详解
2020/04/13 Python
一篇文章带你搞定Ubuntu中打开Pycharm总是卡顿崩溃
2020/11/02 Python
Django启动时找不到mysqlclient问题解决方案
2020/11/11 Python
Sofmap官网:日本著名的数码电器专卖店
2017/05/19 全球购物
购买正版游戏和游戏激活码:Green Man Gaming
2019/11/06 全球购物
英语专业大学生求职简历的自我评价
2013/10/18 职场文书
人事专员岗位职责
2013/11/20 职场文书
开展读书活动总结
2014/06/30 职场文书
办公用房租赁协议书
2014/11/29 职场文书
交通事故被告代理词
2015/05/23 职场文书
2016年世界艾滋病日宣传活动总结
2016/04/01 职场文书
html5中sharedWorker实现多页面通信的示例代码
2021/05/07 Javascript
Matplotlib绘制混淆矩阵的实现
2021/05/27 Python
Redis 常见使用场景
2021/08/30 Redis
深入理解go slice结构
2021/09/15 Golang
日本动漫十大公认神作:第五现已全网禁播,《死亡笔记》在榜
2022/03/18 日漫