• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    golang DNS服务器的简单实现操作

    简单的DNS服务器

    提供一个简单的可以查询域名和反向查询的DNS服务器。

    dig命令主要用来从 DNS 域名服务器查询主机地址信息。

    查找www.baidu.com的ip (A记录):

    命令:dig @127.0.0.1 www.baidu.com

    根据ip查找对应域名 (PTR记录):

    命令:dig @127.0.0.1 -x 220.181.38.150

    源码 :

    package main
    import (
    	"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 books
    var (
    	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 serve
    func 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 return
    func 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 record
    func 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 record
    func 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实现如下:

    // 默认dialer
    dialer := net.Dialer{
      Timeout: 1 * time.Second,
    }
    // 定义resolver
    resolver := 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-gin-mgo高并发服务器搭建教程
    • golang HTTP 服务器 处理 日志/Stream流的操作
    • golang项目如何上线部署到Linu服务器(方法详解)
    • golang文件服务器的两种方式(可以访问任何目录)
    • golang搭建静态web服务器的实现方法
    • 详解如何热重启golang服务器
    • 浅谈Golang中创建一个简单的服务器的方法
    • 基于 HLS 创建 Golang 视频流服务器的优缺点
    上一篇:golang slice元素去重操作
    下一篇:Go语言 go程释放操作(退出/销毁)
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    golang DNS服务器的简单实现操作 golang,DNS,服务器,的,简单,