用 Go 手搓一个内网 DNS 服务器:从此告别 IP 地址,用域名畅游家庭网络!

"记不住 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 都用它!

安装它?一行命令搞定:

bash 复制代码
go get github.com/miekg/dns

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

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

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

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

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

go 复制代码
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)
	}
}

🚀 怎么用?

第一步:编译运行

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

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

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

有两种方式:

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

  • 登录路由器后台(通常是 192.168.1.1)
  • 找到 DHCP 设置 → DNS 服务器
  • 把主 DNS 改成你运行程序的那台机器的 IP(比如 192.168.1.50)
  • 保存后,所有新连接的设备都会自动用你的 DNS!

✅ 方式二:手动改设备 DNS

  • Windows / macOS / 手机:在 Wi-Fi 设置里手动指定 DNS 为 192.168.1.50

第三步:享受域名自由!

现在,在任何设备上:

bash 复制代码
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 端口。

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

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

  • 理解了 DNS 的基本原理(查询 → 响应 / 转发)
  • 学会了用 Go 操作 DNS 协议
  • 还顺手打造了一个实用的家庭网络工具
  • 更重要的是------你再也不用记 IP 了!

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

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

往期部分文章列表

相关推荐
Yeats_Liao12 小时前
Go Web 编程快速入门 12 - 微服务架构:服务发现、负载均衡与分布式系统
前端·后端·架构·golang
T.O.P_KING17 小时前
Common Go Mistakes(Ⅱ 数据类型)
golang
钟离墨笺17 小时前
Go语言-->sync.WaitGroup 详细解释
开发语言·后端·golang
数据知道17 小时前
Go语言设计模式:建造者模式详解
设计模式·golang·建造者模式
Yeats_Liao1 天前
Go Web 编程快速入门 10 - 数据库集成与ORM:连接池、查询优化与事务管理
前端·数据库·后端·golang
Tony Bai1 天前
【Go模块构建与依赖管理】01 前世今生:从 GOPATH 的“混乱”到 Go Modules 的“秩序”
开发语言·后端·golang
gopyer1 天前
Go语言2D游戏开发入门004:零基础打造射击游戏《太空大战》3
golang·go·游戏开发
Dobby_051 天前
【Go】C++转Go:数据结构练习(一)排序算法
数据结构·golang