golang知识集

用 Go 手搓一个内网 DNS 服务器:从此告别 IP 地址,用

2025-11-11  本文已影响0人  Mgx_无心

"记不住 192.168.1.103?那就给它起个名字!"

大家好,我是你们的"家庭网络摆渡人"。今天不聊 5G,也不谈元宇宙,咱们干一件特别"复古"但又超实用的事——在内网搭一个自己的 DNS 服务器!

你可能会问:DNS 是啥?能吃吗?

别急,先想象一个场景:

你有一台 NAS(网络存储),IP 是 192.168.1.100
一台 Git 服务器,IP 是 192.168.1.101
还有一台打印机,IP 是 192.168.1.102
每次想访问它们,你都得输入一串数字……是不是有点像在背电话号码?而且一旦 IP 变了,全家设备都得重新配置。

有没有办法像访问 baidu.com 一样,用 nas.local、git.home 这种好记的名字?

答案是:有!而且只需要几十行 Go 代码 + 一个神奇的第三方库!

🧠 DNS 是什么?30 秒科普

DNS(Domain Name System)就是互联网的"电话簿"。

你输入 www.qq.com → DNS 告诉你它对应的 IP 是 111.161.64.48
没有 DNS,你就得记住成千上万个 IP,那画面太美我不敢看 😅
而在内网,我们也可以建一个"小电话簿",只管我们自己家的设备。

🛠 我们用什么工具?

Go 语言 + 一个超好用的开源库:miekg/dns

这个库由 DNS 领域的大神 Miek Gieben 开发,功能强大、文档清晰,连 Kubernetes 的 CoreDNS 都用它!

安装它?一行命令搞定:

go get github.com/miekg/dns

🧪 核心思路:自定义 + 转发

我们的 DNS 服务器要做两件事:

  1. 如果查询的是我"认识"的域名(比如 nas.local),直接返回对应的内网 IP。
  2. 如果是其他域名(比如 baidu.com、github.com),就转发给真正的 DNS(比如你家的路由器 192.168.1.1)去查。

这就叫 "权威 + 递归"混合模式——听起来高大上,其实逻辑超简单!

💻 全部源码奉上(带中文日志!)

package main

import (
    "fmt"
    "log"
    "net"

    "github.com/miekg/dns"
)

// 上游 DNS 服务器(你的路由器或者公网 DNS 地址)
var upstreamDNS = "192.168.1.1:53"

// 自定义解析的域名映射(注意:域名必须以点 . 结尾!)
var customRecords = map[string]string{
    "a.local.": "192.168.1.100",
    "b.local.": "192.168.1.101",
    "c.git.":   "218.95.11.11",
    // 你可以继续添加:
    // "printer.home.": "192.168.1.102",
    // "camera.lan.":   "192.168.1.103",
}

func main() {
    addr := ":53" // 监听 UDP 53 端口(标准 DNS 端口)
    server := &dns.Server{Addr: addr, Net: "udp"}

    dns.HandleFunc(".", handleDNSRequest)

    fmt.Printf("正在 %s 启动 DNS 服务器,其他请求将转发至 %s...\n", addr, upstreamDNS)
    if err := server.ListenAndServe(); err != nil {
        log.Fatalf("启动服务器失败:%v\n", err)
    }
}

func handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
    if len(r.Question) == 0 {
        return
    }
    q := r.Question[0]

    // 检查是否命中自定义域名
    if ipStr, ok := customRecords[q.Name]; ok && q.Qtype == dns.TypeA {
        fmt.Printf("自定义解析:%s -> %s\n", q.Name, ipStr)
        m := new(dns.Msg)
        m.SetReply(r)
        rr := &dns.A{
            Hdr: dns.RR_Header{
                Name:   q.Name,
                Rrtype: dns.TypeA,
                Class:  dns.ClassINET,
                Ttl:    60, // 缓存 60 秒
            },
            A: net.ParseIP(ipStr).To4(),
        }
        m.Answer = append(m.Answer, rr)
        if err := w.WriteMsg(m); err != nil {
            log.Printf("发送自定义响应失败:%v\n", err)
        }
        return
    }

    // 未命中?转发给上游 DNS!
    fmt.Printf("正在将 %s 的查询请求转发至 %s\n", q.Name, upstreamDNS)
    client := new(dns.Client)
    resp, _, err := client.Exchange(r, upstreamDNS)
    if err != nil {
        log.Printf("转发查询请求失败:%v\n", err)
        m := new(dns.Msg)
        m.SetRcode(r, dns.RcodeServerFailure)
        w.WriteMsg(m)
        return
    }

    if err := w.WriteMsg(resp); err != nil {
        log.Printf("发送转发响应失败:%v\n", err)
    }
}

🚀 怎么用?

第一步:编译运行

go build -o mydns main.go
sudo ./mydns  # 需要 root 权限才能监听 53 端口!

💡 小技巧:开发时可以用 :1053 端口避免权限问题,用 dig @localhost -p 1053 a.local 测试。

第二步:让设备使用你的 DNS

有两种方式:

✅ 方式一:改路由器 DHCP 设置(推荐!)

✅ 方式二:手动改设备 DNS

第三步:享受域名自由!

现在,在任何设备上:

ping a.local      # → 192.168.1.100
curl http://b.local
ssh user@nas.local  # 如果你加了 nas.local.

是不是瞬间感觉家里设备"活"起来了?😎

⚠️ 注意事项(避坑指南)

  1. 域名必须带结尾的点!

    • a.local. ✅
    • a.local ❌(DNS 协议要求 FQDN 以点结尾)
  2. 别用真实公网域名!

    • 比如别写 "baidu.com.",否则你可能再也打不开百度了……
    • 建议用 .local、.home、.lan、.internal 等私有后缀。
  3. 确保你的 DNS 服务器一直在线

    • 如果它挂了,且设备只配置了这一个 DNS,可能导致"上不了网"。
    • 所以保留"转发到路由器"的逻辑非常重要!
  4. 防火墙别拦着 UDP 53 端口

    • 确保内网其他设备能访问这台机器的 53 端口。

🌈 结语:你也可以成为"内网上帝"

通过这个小项目,你不仅:

📌 源码已附上,快去试试吧!

✨ 小彩蛋:如果你把这段代码跑在树莓派上,再配上 UPS 电源,你就拥有了一个 7x24 小时在线的"家庭域名管家"!

往期部分文章列表

上一篇 下一篇

猜你喜欢

热点阅读