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 http使用踩过的坑与填坑指南
Apr 27 Golang
golang 实现对Map进行键值自定义排序
Apr 28 Golang
Golang 空map和未初始化map的注意事项说明
Apr 29 Golang
浅谈golang package中init方法的多处定义及运行顺序问题
May 06 Golang
Go语言实现Snowflake雪花算法
Jun 08 Golang
go语言使用Casbin实现角色的权限控制
Jun 26 Golang
Go语言应该什么情况使用指针
Jul 25 Golang
使用GO语言实现Mysql数据库CURD的简单示例
Aug 07 Golang
golang实现一个简单的websocket聊天室功能
Oct 05 Golang
golang用type-switch判断interface的实际存储类型
Apr 14 Golang
Golang 遍历二叉树
Apr 19 Golang
Golang入门之计时器
May 04 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/01 无线电
php allow_url_include的应用和解释
2010/04/22 PHP
Laravel框架模板加载,分配变量及简单路由功能示例
2018/06/11 PHP
一个简单的js动画效果代码
2010/07/20 Javascript
javascript:window.open弹出窗口的位置问题
2014/03/18 Javascript
详解JavaScript中循环控制语句的用法
2015/06/03 Javascript
javascript实现多栏闭合展开式广告位菜单效果实例
2015/08/05 Javascript
jQuery实用技巧必备(上)
2015/11/02 Javascript
jquery.qtip提示信息插件用法简单实例
2016/06/17 Javascript
Javascript中prototype的使用详解
2016/06/18 Javascript
基于JavaScript Array数组方法(新手必看篇)
2016/08/20 Javascript
js闭包用法实例详解
2016/12/13 Javascript
vue图片加载与显示默认图片实例代码
2017/03/16 Javascript
es6学习笔记之Async函数的使用示例
2017/05/11 Javascript
Angular4学习笔记之新建项目的方法
2017/07/18 Javascript
Angular4.0中引入laydate.js日期插件的方法教程
2017/12/25 Javascript
浅谈在react中如何实现扫码枪输入
2018/07/04 Javascript
在React中写一个Animation组件为组件进入和离开加上动画/过度效果
2019/06/24 Javascript
js实现扫雷源代码
2020/11/27 Javascript
[10:53]2018DOTA2国际邀请赛寻真——EG
2018/08/11 DOTA
python3使用PyMysql连接mysql数据库实例
2017/02/07 Python
Python实现的redis分布式锁功能示例
2018/05/29 Python
python操作redis方法总结
2018/06/06 Python
浅谈tensorflow中几个随机函数的用法
2018/07/27 Python
NLTK 3.2.4 环境搭建教程
2018/09/19 Python
Tensorflow的梯度异步更新示例
2020/01/23 Python
基于python 等频分箱qcut问题的解决
2020/03/03 Python
Python Flask框架实现简单加法工具过程解析
2020/06/03 Python
canvas像素画板的实现代码
2018/11/21 HTML / CSS
莱德杯高尔夫欧洲官方商店:Ryder Cup Shop
2019/08/14 全球购物
Java方面的关于数组和继承的笔面试题
2015/09/18 面试题
车间调度岗位职责
2013/11/30 职场文书
大学生冰淇淋店商业计划书
2014/01/14 职场文书
大学生水果店创业计划书
2014/01/28 职场文书
个性发展自我评价
2014/02/11 职场文书
2016年清明节红领巾广播稿
2015/12/17 职场文书