需求
一台用于非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,把这个参数存入配置文件:
1 |
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服务器:
1 |
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执行以下命令:
1 |
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格式:
1 2 3 4 |
[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客户端就完事了。
1 2 |
/ip dhcp-client add disabled=no interface=ether1 |
DNS和NTP服务器分别配置成公共服务,不要依赖本地服务。
1 2 3 4 5 |
/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发行通道上的话,鉴于它漏洞那么多,还是自动更新比较好。
1 2 3 4 5 6 7 8 9 10 11 |
/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
里写上:
1 |
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
里写上:
1 2 |
[Time] NTP=ntp.felic.at time.asia.apple.com |
设置一下sysctl:
1 2 |
kernel.sysrq=1 kernel.panic=10 |
安装VMWare Tools
1 |
sudo apt install open-vm-tools |
配置ZeroTier One
1 2 3 4 5 |
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 |
检查资源占用
1 2 3 4 |
$ 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。实现很简单。
首先打开系统的转发功能(注意持久化):
1 2 |
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:
1 2 3 4 |
$ 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服务来启动它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[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。)
检查资源占用
1 2 3 4 |
$ 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