用 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 小时在线的"家庭域名管家"!

往期部分文章列表

相关推荐
平头哥AI10 小时前
Day 22 _ 包装错误别丢链_%w、errors.Is 与 errors.As
android·服务器·学习·golang·go
golang学习记15 小时前
Go 1.27新特性: json/v2使用有趣指南
开发语言·golang·json
王码码203516 小时前
Go语言CGO:Go与C交互
后端·golang·go·接口
PC2005-cloud17 小时前
DSH 白嫖指南:接入 Command Code Go、WorkBuddy 与 Trae 的免费额度
开发语言·后端·golang
名字还没想好☜19 小时前
Docker 镜像瘦身进阶:用 distroless/scratch 把 Go 服务打到 10MB,以及没 shell 怎么调试
运维·docker·容器·golang·kubernetes
lvshuocool19 小时前
golang 多版本管理工具 -- g
开发语言·后端·golang
海盗12341 天前
DSH 接入 OpenCode Go 报 400 MissingSessionID:从 LLM 语义层退到 fetch 传输层的完整排障
开发语言·后端·golang
web守墓人1 天前
【goed/ui】go开发windows原生应用之Helloworld篇
windows·ui·golang
平头哥AI1 天前
Day 21 _ error 是个普通值_errors.New 与 fmt.Errorf 造出来,沿调用栈抛到 main 接住
后端·学习·golang·go
HotCoffee-GPS2 天前
Golang性能分析pprof
golang