"记不住 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 服务器要做两件事:
- 如果查询的是我"认识"的域名(比如 nas.local),直接返回对应的内网 IP。
- 如果是其他域名(比如 baidu.com、github.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.
是不是瞬间感觉家里设备"活"起来了?😎
⚠️ 注意事项(避坑指南)
-
域名必须带结尾的点!
- a.local. ✅
- a.local ❌(DNS 协议要求 FQDN 以点结尾)
-
别用真实公网域名!
- 比如别写 "baidu.com.",否则你可能再也打不开百度了......
- 建议用 .local、.home、.lan、.internal 等私有后缀。
-
确保你的 DNS 服务器一直在线
- 如果它挂了,且设备只配置了这一个 DNS,可能导致"上不了网"。
- 所以保留"转发到路由器"的逻辑非常重要!
-
防火墙别拦着 UDP 53 端口
- 确保内网其他设备能访问这台机器的 53 端口。
🌈 结语:你也可以成为"内网上帝"
通过这个小项目,你不仅:
- 理解了 DNS 的基本原理(查询 → 响应 / 转发)
- 学会了用 Go 操作 DNS 协议
- 还顺手打造了一个实用的家庭网络工具
- 更重要的是------你再也不用记 IP 了!
📌 源码已附上,快去试试吧!
✨ 小彩蛋:如果你把这段代码跑在树莓派上,再配上 UPS 电源,你就拥有了一个 7x24 小时在线的"家庭域名管家"!
往期部分文章列表
- 我用Go写了个华容道游戏,曹操终于不用再求关羽了!
- 用 Go 接口把 Excel 变成数据库:一个疯狂但可行的想法
- 穿墙术大揭秘:用 Go 手搓一个"内网穿透"神器!
- 布隆过滤器(go):一个可能犯错但从不撒谎的内存大师
- 自由通讯的魔法:Go从零实现UDP/P2P 聊天工具
- Go语言实现的简易远程传屏工具:让你的屏幕「飞」起来
- 当你的程序学会了"诈尸":Go 实现 Windows 进程守护术
- 验证码识别API:告别收费接口,迎接免费午餐
- 用 Go 给 Windows 装个"顺风耳":两分钟写个录音小工具
- 无奈!我用go写了个MySQL服务
- 使用 Go + govcl 实现 Windows 资源管理器快捷方式管理器
- 用 Go 手搓一个 NTP 服务:从"时间混乱"到"精准同步"的奇幻之旅
- 用 Go 手搓一个 Java 构建工具:当 IDE 不在身边时的自救指南
- 深入理解 Windows 全局键盘钩子(Hook):拦截 Win 键的 Go 实现
- 用 Go 语言实现《周易》大衍筮法起卦程序
- Go 语言400行代码实现 INI 配置文件解析器:支持注释、转义与类型推断
- 高性能 Go 语言带 TTL 的内存缓存实现:精确过期、自动刷新、并发安全
- Golang + OpenSSL 实现 TLS 安全通信:从私有 CA 到动态证书加载