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项目中使用init()
Apr 12 Golang
golang判断key是否在map中的代码
Apr 24 Golang
goland 恢复已更改文件的操作
Apr 28 Golang
Go语言实现Snowflake雪花算法
Jun 08 Golang
浅谈Go语言多态的实现与interface使用
Jun 16 Golang
Golang 语言控制并发 Goroutine的方法
Jun 30 Golang
Go语言基础函数基本用法及示例详解
Nov 17 Golang
详解Golang如何优雅的终止一个服务
Mar 21 Golang
golang为什么要统一错误处理
Apr 03 Golang
Golang 1.18 多模块Multi-Module工作区模式的新特性
Apr 11 Golang
GO语言字符串处理函数之处理Strings包
Apr 14 Golang
Golang jwt身份认证
Apr 20 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 模板高级篇总结
2006/12/21 PHP
php 静态变量的初始化
2009/11/15 PHP
PHP学习笔记之一
2011/01/17 PHP
PHP数组 为文章加关键字连接 文章内容自动加链接
2011/12/29 PHP
PHP批量采集下载美女图片的实现代码
2013/06/03 PHP
PHP实现返回JSON和XML的类分享
2015/01/28 PHP
php添加数据到xml文件的简单例子
2016/09/08 PHP
如何使用PHP给图片加水印
2016/10/12 PHP
搭建自己的PHP MVC框架详解
2017/08/16 PHP
javascript Object与Function使用
2010/01/11 Javascript
jQuery EasyUI API 中文文档 - Panel面板
2011/09/30 Javascript
js nextSibling属性和previousSibling属性概述及使用注意
2013/02/16 Javascript
js+csss实现的一个带复选框的下拉框
2014/09/29 Javascript
node.js中的fs.fstatSync方法使用说明
2014/12/15 Javascript
JavaScript的设计模式经典之代理模式
2016/02/24 Javascript
jQuery插件jqGrid动态获取列和列字段的方法
2017/03/03 Javascript
JavaScript用二分法查找数据的实例代码
2017/06/17 Javascript
JS中call()和apply()的功能及用法实例分析
2019/06/28 Javascript
json字符串对象转换代码实例
2019/09/28 Javascript
Vue使用Three.js加载glTF模型的方法详解
2020/06/14 Javascript
Js Snowflake(雪花算法)生成随机ID的实现方法
2020/08/26 Javascript
举例讲解Python中装饰器的用法
2015/04/27 Python
Python通过future处理并发问题
2017/10/17 Python
Python中将变量按行写入txt文本中的方法
2018/04/03 Python
基于python的图片修复程序(实现水印去除)
2018/06/04 Python
对Pycharm创建py文件时自定义头部模板的方法详解
2019/02/12 Python
TFRecord文件查看包含的所有Features代码
2020/02/17 Python
Django:使用filter的pk进行多值查询操作
2020/07/15 Python
Python命名空间及作用域原理实例解析
2020/08/12 Python
CSS3 Backgrounds属性相关介绍
2011/05/11 HTML / CSS
凯特王妃父母建立的派对用品网站:Party Pieces
2017/05/28 全球购物
泰国演唱会订票网站:StubHub泰国
2018/02/26 全球购物
应届生服务员求职信
2013/10/31 职场文书
清洁工个人总结
2015/03/04 职场文书
公司年会主持词范文!
2019/05/07 职场文书
详解JAVA的控制语句
2021/11/11 Java/Android