需求
一台用于非 IT 专业人士家的小服务器,需要运行诸如 DNS 分流和代理之类的软件供内网使用。它处于 Full Cone NAT 之后,并且需要能被远程管理和重新配置。它应当不发出噪音,并且尽量节约用电。
设备选型
考虑到这台设备上会跑一些网络应用,我需要一个比较能用的 CPU 加上多于一个的以太网端口。在淘宝上购买了某家专门做无风扇 small form factor 机器厂的产品,拆掉里面不靠谱的垃圾配件,组成了以下配置:
- Intel Celeron J1900
- 自己换的 8G 三星内存
- HP 拆机的 Intel S3610 200G,存储 Hypervisor 和关键应用数据
- Samsung 860 EVO mSATA 256G,存储非关键应用数据
- 两个 Intel I211 网卡
- 12V 3A 外置电源
其实本来想选择一个支持 ECC 内存的 CPU 的,但是这个价格上确实也不好找,遂放弃。J1900 让我不满的另一点是不支持 SR-IOV,这样机子上自带的一块 Intel 5100 Wi-Fi 网卡就算是废了,只能把它拆掉了。不用的端口,例如 COM 和音频输入输出口可以用相应的防尘塞堵上;拆掉网卡以后露出来的天线口用黑色的电工胶布贴了一下,防止进灰。然后买了一个 VGA 的假负载用于绕过一个固件 bug。
固件配置
这台设备因为是深圳小作坊出品,其 UEFI 固件基本上是 Aptio 提供给 Bay Trail 设备的参考固件,设置非常全面,一个都没隐藏。固件版本为 GBYT9 V1.02,坑有几个:
- 使用 UEFI GOP 的时候,HDMI 输出不可用,只能用 VGA
- 如果没有连接显示器,固件会死机,无法启动系统
- 如果设备上连接了多个 USB 键盘,只有第一个被枚举的能在固件中使用,因此尽量不要在机子上插没用的键盘或者无线接收器之类的玩意
固件里面需要修改的设置不多:
- 关掉 Legacy Option ROM(然后需要重启一下)
- 关掉 CSM
- 打开 Secure Boot(ESXi 6.7 支持 Secure Boot 和 TPM2.0 了,虽然没有 vCenter 的话 TPM 用不上)
- 在南桥设置中,设置掉电后自动开机
- 在北桥设置中,把显卡的睿频关掉,显存分配全部设为最小
Hypervisor 配置
考虑到设备后期扩展和维护的需要,这台服务器上会运行一个 Hypervisor,所有应用软件放进虚拟机。那么常见的免费 Hypervisor 有这么几个选项:
- Microsoft Hyper-V Server
- Proxmox VE
- VMWare ESXi
Proxmox VE 的不稳定和设计糟糕是所有用过的人都明白的。Microsoft Hyper-V Server 在非 Active Directory 域环境中很不好使,远程管理配置困难,命令行体验也非常糟糕。最后我决定用 ESXi,相对来讲比较轻量级,对各种操作系统的支持都比较好,远程管理处于能用水平,并且免费版提供的功能对家用环境来说也够用。
VMWare ESXi 安装
很幸运,Intel I211 网卡是 ESXi 6.7 原生驱动支持的网卡。如果系统上没有 ESXi 自带驱动的网卡的话,你就得去第三方网站上找找有没有相应的驱动了。
安装过程感觉没什么好讲的,安装向导一步步走完就是了。唯一需要注意的是,ESXi 默认会把管理网所在的 vmk0 挂到第一个枚举到的网卡下面,对于多网卡设备,可能需要试一下具体是哪个网卡。为了方便区分,我打印了一张贴纸贴在机器外壳上。

在我这个配置的机子上,全新安装的 ESXi 6.7 启动时间约 2 分钟。
另外 ESXi 6.7 Build 13006603 及之前的版本好像和 Chrome 73 及以上版本有一些兼容性问题,如果 ESXi 网页打不开,可以试一下 Firefox 或者其它非 Chrome 内核的浏览器。
内核参数
我这台设备比较诡异,安装程序启动和安装完成第一次重启的时候,需要在 bootloader 界面按 Shift+O,在内核参数的末尾添加 ignoreHeadless=TRUE
,否则启动内核的时候会卡住。
第一次启动成功后,通过 console shell 或者 SSH,把这个参数存入配置文件:
|
esxcfg-advcfg --set-kernel "TRUE" ignoreHeadless |
然后重启一下,确认系统能在无用户干预情况下自动启动。
主机名
前往 Networking -> TCP/IP stacks -> Default TCP/IP stack -> Edit settings,在 Basic DNS configuration 里面选择 Manually configure the settings for this TCP/IP stack,在 Host name 一项中输入你想要的主机名。应用配置后,再把设置改回 Use DHCP DNS。这样网络中的 DHCP 设置会继续生效,但是主机名已经改成你自己设置的了。
如果你考虑给 ESXi 加 Active Directory 域的话,建议主机名长度不要超过 15 字符,不然和 NetBIOS 协议会有兼容性问题。
NTP
为了保证在中国大陆的可用性,设置了以下 NTP 服务器:
|
time.asia.apple.com, ntp.felixc.at, time.nist.gov, cn.pool.ntp.org |
设置完以后需要手工启动一下 NTP 服务。同步需要过几分钟才会完成,不需着急。

第二块硬盘
前往 Storage -> Datastores,新建一个 datastore 即可,也没有什么可以说的。
SFCBD
启用 SFCBD(Small Footprint CIM Broker Daemon)我们才能读到一些硬件的健康数据。不过这个比较看机型,我这台机子上就基本上读不到有效信息。通过 console shell 或者 SSH 执行以下命令:
|
esxcli system wbem set --enable true |
用户
如果有需要的话,前往 Host -> Manage -> Security & users -> Users 创建用户。需要注意的是,ESXi 默认会禁止最近一段时间登录失败次数过多的用户登录,所以如果你只有默认的 root 用户并且机子还暴露在公网上的话,很可能因为有人爆破密码而把你自己关在外面,因此建议至少创建一个用户名不常见的用户。
虚拟机配置
我们只有 8GiB 内存,硬件会占用大概 0.11GiB,ESXi 本身会吃掉 1.22GiB(如果打开 swap 会稍微少一点),那么满打满算剩下来的还有 6.67GiB。在这点内存里面要塞进大量功能,那么每一点都不能浪费。FlexVPN 之类的方案虽然非常好用,但是实在是太吃内存,暂时不予考虑。
远程管理方案 1:RouterOS
选择 RouterOS 的唯一原因是,它能只用 256MiB 内存启动,并且有各种路由功能,非常省资源,如果应急需要用它来当路由器,它也能撑一阵子。RouterOS 的缺点是 bug 多,而且修 bug 比较慢。除去应急时可以用的路由功能不谈,RouterOS 的 OpenVPN 可以提供一个同地区不同站点之间稳定的站到站连接。
因为这台虚拟机是作为远程管理用的,它不应当依赖任何当前站点的服务。
安装 RouterOS
前往官网下载最新的 Cloud Hosted Router 的 VMDK 镜像,传到 ESXi 上,然后把 VMDK 转成 ESXi 支持的 flat 格式:
|
[root@localhost:/vmfs/volumes/system/RouterOS] vmkfstools -i chr-6.43.14.vmdk chr-6.43.14-new.vmdk Destination disk format: VMFS zeroedthick Cloning disk 'chr-6.43.14.vmdk'... Clone: 100% done. |
这样会生成两个文件,一个结尾有 flat(这个文件在网页管理面板上看不到),一个没有,这两个文件组成了整个 flat 格式的 VMDK。如果觉得在 ESXi 上操作麻烦,qemu-img 也可以做相应的格式转换。文件创建出来以后就不能重命名,否则会从系统中消失。
接下来只要正常创建一个 Other Linux 3.x (64bit) 类型的虚拟机,挂载现有硬盘启动就可以了。
配置网络
启用 ipv6 包,重启一下。然后在 ether1 上配一个 DHCP 客户端就完事了。
|
/ip dhcp-client add disabled=no interface=ether1 |
DNS 和 NTP 服务器分别配置成公共服务,不要依赖本地服务。
|
/ip dns set query-total-timeout=20s servers=\ 114.114.114.114,114.114.115.115,223.5.5.5,223.6.6.6,1.2.4.8,210.2.4.8 /system ntp client set enabled=yes server-dns-names=ntp.felixc.at,time.asia.apple.com |
设置远程管理
创建一个 OpenVPN 客户端,连接到我的服务器上。
自动更新
虽然 RouterOS 的更新没那么靠谱,但是如果你在 long-term 发行通道上的话,鉴于它漏洞那么多,还是自动更新比较好。
|
/system script add dont-require-permissions=no name=update owner=admin policy=\ ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/sy\ stem package update\r\ \ncheck-for-updates once\r\ \n:delay 1s;\r\ \n:if ( [get status] = \"New version is available\") do={ install }" /system scheduler add interval=1d name=autoupdate on-event="/system script run update" policy=\ ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \ start-date=apr/28/2019 start-time=04:00:00 |
远程管理方案 2:Linux + ZeroTier One
从节约资源的角度考虑,我选择了 Debian 9,分配了 2C/512MiB/32GB 的资源,另外限制了 30000IOPS。经验表明,在 ESXi 上,一旦你的 Guest OS 在疯狂 IO(比如不小心开了 swap 然后不小心用完了内存),很有可能因为 IO 速度而把 ESXi 自己也一起拖死,因此一定要根据你的硬盘实际能力,对 IOPS 做相应的限制。
安装系统
需要安装 standard system utilities + SSH server,然后关掉 swap。
在/etc/dhcp/dhclient.conf
里写上:
|
prepend domain-name-servers 114.114.114.114,223.5.5.5,223.6.6.6,114.114.115.115,1.2.4.8,210.2.4.8; |
在/etc/systemd/timesyncd.conf
里写上:
|
[Time] NTP=ntp.felic.at time.asia.apple.com |
设置一下 sysctl:
|
kernel.sysrq=1 kernel.panic=10 |
安装 VMWare Tools
|
sudo apt install open-vm-tools |
配置 ZeroTier One
|
sudo apt install curl curl -s 'https://raw.githubusercontent.com/zerotier/download.zerotier.com/master/htdocs/contact%40zerotier.com.gpg' | gpg --import && \ if z=$(curl -s 'https://install.zerotier.com/' | gpg); then echo "$z" | sudo bash; fi sudo zerotier-cli join xxxx sudo zerotier-cli set xxxx allowGlobal=1 |
检查资源占用
|
$ free -wh total used free shared buffers cache available Mem: 473M 107M 126M 2.4M 13M 225M 351M Swap: 0B 0B 0B |
应用容器:Linux + Docker
这一台虚拟机给了 2C/2GiB/64GB。安装系统和初始配置的方法和上一台 Linux 虚拟机类似,不再赘述。
代理网关
TCP 部分可以在 iptables 的 nat 表上做 REDIRECT;UDP 部分可以使用 iptable/netfilter 的 TPROXY 功能实现。TPROXY 需要 Linux 内核版本 4.18 以上,所以我在这台机子上用了内核版本 4.19 的 Debian Buster。实现很简单。
首先打开系统的转发功能(注意持久化):
|
sysctl net.ipv4.ip_forward=1 sysctl net.ipv6.conf.all.forwarding=1 |
TCP 这边的话,简单设置所有公网流量重定向到代理所在端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
iptables -t nat -N PROXY iptables -t nat -A PROXY -d 0.0.0.0/8 -j RETURN iptables -t nat -A PROXY -d 10.0.0.0/8 -j RETURN iptables -t nat -A PROXY -d 100.64.0.0/10 -j RETURN iptables -t nat -A PROXY -d 127.0.0.0/8 -j RETURN iptables -t nat -A PROXY -d 169.254.0.0/16 -j RETURN iptables -t nat -A PROXY -d 172.16.0.0/12 -j RETURN iptables -t nat -A PROXY -d 192.168.0.0/16 -j RETURN iptables -t nat -A PROXY -d 224.0.0.0/4 -j RETURN iptables -t nat -A PROXY -d 240.0.0.0/4 -j RETURN # exclude docker iptables -t nat -A PROXYCORE -s 172.17.0.0/16 -j RETURN iptables -t nat -A POSTROUTING -o ens192 -j MASQUERADE iptables -t nat -A PROXY -p tcp -j REDIRECT --to-ports 1919 iptables -t nat -A PREROUTING -p tcp -j PROXY ip rule add fwmark 0x01 lookup 100 ip route add local 0.0.0.0/0 dev lo table 100 ip6tables -t nat -N PROXY ip6tables -t nat -A PROXY -p tcp -d 2000::/3 -j REDIRECT --to-ports 1919 ip6tables -t nat -A PREROUTING -p tcp -j PROXY ip -6 rule add fwmark 0x01 lookup 100 ip -6 route add local default dev lo table 100 |
UDP 则是使用 TPROXY 规则在 mangle 上拦截一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
iptables -t mangle -N PROXY_UDP iptables -t mangle -A PROXY_UDP -d 0.0.0.0/8 -j RETURN iptables -t mangle -A PROXY_UDP -d 10.0.0.0/8 -j RETURN iptables -t mangle -A PROXY_UDP -d 100.64.0.0/10 -j RETURN iptables -t mangle -A PROXY_UDP -d 127.0.0.0/8 -j RETURN iptables -t mangle -A PROXY_UDP -d 169.254.0.0/16 -j RETURN iptables -t mangle -A PROXY_UDP -d 172.16.0.0/12 -j RETURN iptables -t mangle -A PROXY_UDP -d 192.168.0.0/16 -j RETURN iptables -t mangle -A PROXY_UDP -d 224.0.0.0/4 -j RETURN iptables -t mangle -A PROXY_UDP -d 240.0.0.0/4 -j RETURN iptables -t mangle -A PROXY_UDP -p udp -j TPROXY --on-port 1919 --tproxy-mark 0x01/0x01 iptables -t mangle -A PROXY_UDP -p udp -j MARK --set-mark 1 iptables -t mangle -A PREROUTING -j PROXY_UDP ip6tables -t mangle -N PROXY_UDP ip6tables -t mangle -A PROXY_UDP -p udp -d 2000::/3 -j TPROXY --on-port 1919 --tproxy-mark 0x01/0x01 ip6tables -t mangle -A PREROUTING -p udp -j PROXY_UDP |
然后分别在 1919 端口上启动相应代理程序的 TCP 和 UDP 监听即可。需要注意的是,代理程序自己的流量不能走这条规则,那么就要自行想办法做区分。调整 iptables 规则的时候要注意一下不要把 Docker 的默认 NAT 规则清掉了。另外如果你的 IPv6 是 SLAAC 配置的话,给用户下发网关可能比较麻烦,需要自己想一下办法。
DNS 分流:dnsdist-autoconf + Pi-Hole
我们首先需要安装 Docker。Docker 有一个大坑就是它默认会把 iptables 的 forwarding chain 的默认规则改成 DROP,这样你的路由器莫名其妙就坏了。我们需要配置 Docker 不要没事折腾 iptables:
|
$ cat /etc/docker/daemon.json { "iptables": false } |
配置完以后重启一下 Docker daemon。这样配置有安全风险,请自行考量。
安装 dnsdist-autoconf 很简单,写一个基础配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
# global config # if an error is encountered, stop quit_on_error = false listen = [ "0.0.0.0:53", "[::]:53", ] # default upstream upstreams = [ "202.141.162.123:53", "182.254.242.15:53", "208.67.222.222:443", "208.67.220.220:443", ] allowed_client_subnets = [ # everywhere - potentially unsafe, use with caution "0.0.0.0/0", "::/0", ] # set to true if you use Active Directory and allows DNS update allow_ddns_updates = false # EDNS0 Client Subnet [ecs] enabled = true default_prefix_v4 = 24 default_prefix_v6 = 48 # if DNS request source IP is not a public routable IP, still forward its ECS information to upstream keep_private_ip = false #[control_socket] #listen = "0.0.0.0" #key = "" #[web_server] #listen = "127.0.0.1:8083" #password = "supersecretpassword" #api_key = "supersecretAPIkey" # enable cache # note: it eats memory [cache] enabled = true max_entries = 16384 # Rules [[match]] domains = [ "music.httpdns.c.163.com", "zzhc.vnet.cn", ] action = "block" [[match]] provider = "dnsmasq-china-list" upstreams = [ "223.5.5.5:53", "223.6.6.6:53", "114.114.114.114:53", "114.114.115.115:53", ] |
然后写一个简单的 systemd 服务来启动它:
|
[Unit] Description=Auto configured DNS loadbalancer in Docker Requires=docker.service Conflicts=systemd-resolved.service,dnsdist.service [Service] ExecStartPre=-/usr/bin/docker kill dnsdist-autoconf_1 ExecStartPre=-/usr/bin/docker rm dnsdist-autoconf_1 ExecStart=/usr/bin/docker run --rm --name=dnsdist-autoconf_1 --memory=512m -p=127.0.0.1:5302:53/udp -p=127.0.0.1:5302:53/tcp -p=8083:80/tcp -v=/etc/dnsdist:/etc/dnsdist --dns=114.114.114.114 --dns=223.5.5.5 jamesits/dnsdist-autoconf:1.3.7 ExecStop=/usr/bin/docker stop dnsdist-autoconf_1 ExecReload=/usr/bin/docker restart dnsdist-autoconf_1 TimeoutStartSec=infinity [Install] WantedBy=multi-user.target |
(注意这里监听了 5302 端口,因为我们之后会用 Pi-Hole 来间接使用它。)
安装 Pi-Hole,继续用 Docker+systemd:
|
[Unit] Description=Pi-Hole Requires=docker.service [Service] ExecStartPre=-/usr/bin/docker kill pihole ExecStartPre=-/usr/bin/docker rm pihole ExecStart=/usr/bin/docker run --rm --name=pihole --memory=512m -v /etc/pihole:/etc/pihole -v /etc/dnsmasq.d:/etc/dnsmasq.d -e TZ="Asia/Hongkong" -e WEBPASSWORD="password" -e ServerIP=192.168.1.2 -e DNS1="127.0.0.1#5302" -e DNS2="127.0.0.1#5301" --dns=127.0.0.1 --dns=114.114.114.114 --dns=223.5.5.5 --dns 114.114.115.115 --dns 223.6.6.6 --net=host --cap-add=NET_ADMIN pihole/pihole:4.2.2-1 ExecStop=/usr/bin/docker stop pihole ExecReload=/usr/bin/docker restart pihole TimeoutStartSec=infinity [Install] WantedBy=multi-user.target |
(注意这玩意设计很糟糕,需要硬编码本机 IP 地址。上游 DNS 必须写在环境变量里,不能用网页设置。我这里设了用 dnsdist-autoconf 开出来的 localhost:5302 和用另一个 fallback 程序开出来的 localhost:5301。)
检查资源占用
|
$ free -wh total used free shared buffers cache available Mem: 1.9G 440M 1.1G 5.8M 17M 382M 1.4G Swap: 0B 0B 0B |
测试
- Hypervisor 能够在不插键盘和显示器的情况下正常启动
- 虚拟机能在 Hypervisor 启动之后自动启动
- 虚拟机中各个应用程序工作正常,并且不依赖虚拟机的启动顺序
- 所有远程访问方式能够正常工作
清理工作
- 关掉不必要的 Hypervisor 服务:console shell,SSH
- 给所有虚拟机做个 snapshot
On site 安装工作
- 网关虚拟机需要固定 IP
- Pi-Hole 需要设置网关 IP
参考:
mark 一下, 回去把 win10 系统刷成虚拟机加 docker 的形式。
折腾完事老婆说 不如原来 tplink 快。 还费电。 换回去吧。 ! ! ! ! ! ! !
哈哈
软路由的坑
Pingback 引用通告: 在家也要玩 BGP( 2) : 可控地给部分局域网设备设置透明代理网关 | Drown in Codes