Server

collectd + Graphite + Grafana 搭建网络质量监控系统

前段时间入手一台 Gen8 服务器,主要用来做网络存储。光做网络存储显然太浪费了,感谢 ESXi,一机多用很方便。本文介绍如何在家庭服务器上搭建简单好用的网络质量监控系统。

一、选材

说到网络质量监控,大部分人会想到著名的 SmokePing。SmokePing 的确是经典工具,但未免老旧,配置也略复杂。本文使用 collectd 作为收集工具,Graphite 作为存储工具,Grafana 作为展示工具。这些工具符合「专做一件事情并把这件事情做好」的 Unix 哲学,配置灵活、功能强大。

整体结构是这样的:

asciiflow-collectd-graphite-grafana-2

二、收集:collectd

正如其名字所暗示的那样,collectd 是一个收集系统各项指标的进程。它自带很多插件,也可以通过自定义插件和数据类型的方式增加更多的收集项。网络质量监控主要用到其中的 ping 插件,该插件依赖 Liboping 这个库。这两个项目在主流 GNU/Linux 发行版中都有打包。

使用你最喜爱的包管理器安装 collectd 和 liboping 之后,使用你最喜爱的编辑器打开 /etc/collectd.conf 文件。这是一个带有详尽注释的超长配置文件,要让 ping 插件工作,以下是一份示例配置:

Hostname "your-hostname"
FQDNLookup false

LoadPlugin ping
LoadPlugin write_graphite

<Plugin ping>
  Host "8.8.8.8"
  Host "8.8.4.4"
  Interval 1.0
  Timeout 0.9
</Plugin>

<Plugin write_graphite>
  <Node "localhost">
    Host "localhost"
    Port "2003"
    Protocol "tcp"
    LogSendErrors true
  </Node>
</Plugin>

意义很明确,声明自己的主机名(用于上报数据),加载 ping 和 write_graphite 插件,然后配置这两个插件。

ping 插件的配置项中,Host 用于添加需要被监控的目标,一行一个,IntervalTimeout 则分别指定多久 ping 一次,以及多久没收到回包认为是超时。如有特殊需求,还可以指定 SourceAddress, Device 等参数指定从哪个网络设备发出 ICMP 包。注意,这里 Interval 不是指多久上报一次,而是指多久 ping 一次,上报的话还是按全局的来(默认 10 秒),上报时的数据是这段时间 RTT 的算术平均数。

write_graphite 插件的配置项中,填写 carbon-cache.py 监听的地址和端口。本例中跑 collectd 的机器同时也跑了 carbon-cache.py,因此填 localhost 即可。如果它们不在同一台机器上(比如 collectd 跑在路由器上,carbon-cache.py 跑在配置更高的设备上),则需要填写相应的地址。

collectd 发出去的数据是很简单的 TCP 消息,如:

foo.bar.baz 123 1458372405

以空格分隔,第一段是指标名字,第二段是数值,第三段是时间戳。

改好配置之后保存。因为现在 carbon-cache.py 还没有运行,因此还不能启动 collectd。

三、接收、存储和查询:carbon-cache.py 和 graphite-api

Graphite 是一个较大项目,它的主要组件有:

  • Carbon。包括 carbon-cache.py, carbon-relay.py 等,用于接收数据点;
  • Whisper。数据点存储格式,Carbon 用它把数据点写入磁盘;
  • Graphite Web。基于 Django 的网页应用,既提供查询数据点的 API,也提供一个展示用的网页。

由于 Graphite Web 较为臃肿而功能比较弱,因此这里不使用它,而是使用 Graphite-API 这个第三方项目提供查询 API,用漂亮的 Grafana 提供展示页面。

Carbon 可以直接从 PyPI 安装,注意它只支持 Legacy Python。它是纯 Python 的,没有其他依赖,使用 pip2 install carbon 即可轻松安装。

Carbon 使用 Whisper 存储数据点,这一格式的文件大小是预分配的,并且是固定的。旧的数据可以设置自动降低精度,非常旧的数据可以设置丢弃。在 storage-schemas.conf 中可以这么设置:

[ping]
pattern = \.ping\.
retentions = 10s:1d,1m:30d,5m:180d,30m:1y

[default]
pattern = .*
retentions = 1m:1d,5m:30d,30m:180d,1h:1y

其中 pattern 匹配指标的名字,retentions 指定这个指标应该保留多久。以 [default] 为例,这个指标的数据,1 分钟精度的会保留 1 天,之后自动降为 5 分钟精度,保留 30 天,之后自动降为 30 分钟精度,保留 180 天,之后自动降为 1 小时精度,保留 1 年。而对于 [ping] 一节的数据,我希望能更精确一些,因此 1 天内的数据是 10 秒精度的。这样的设置可以使不同的指标按需自动降低精度以节省存储空间,既能查到近期的高精度数据,也能反观远期的大致趋势。

注意这些政策仅对新创建的 .wsp 文件有效,已有的文件的存储策略需要通过 whisper-resize.py 进行更改。
Graphite-API 因为带有非 Python 的图形库依赖,编译安装时较为麻烦。Ubuntu / Debian 用户可以用官方提供的 .deb 安装。Arch Linux 用户可以使用我打包的 aur/graphite-api 安装。

安装之后打开 graphite-api.yaml 文件。根据安装方式的不同,Carbon 和 Graphite-API 的存储路径、配置文件路径会有一些差别,请按照自己的情况将 whisper – directories 一节的路径填写正确。

另外推荐配置 carbonlink,查询那些在 carbon-cache.py 内存中还未写入磁盘的数据。最终我使用的 graphite-api.yaml 内容如下:

finders:
  - graphite_api.finders.whisper.WhisperFinder
whisper:
  directories:
    - /var/lib/carbon/whisper
carbon:
  hosts:
    - 127.0.0.1:7002
  timeout: 1
  retry_delay: 15
  carbon_prefix: carbon
  replication_factor: 1

配置完成后,可以启动 carbon-cache.py 和 graphite-api。上节配置的 collectd 也可以启动了。

四、展示:grafana-server

Grafana 是一个功能强大的图表绘制服务器,支持 Graphite, InfluxDB, OpenTSDB 等多种后端,支持多种图表绘制,几乎无外部依赖。

Grafana 由 Go 和 Node.js 写成,编译结果是一个单文件 Go 程序和一堆 HTML + CSS + JavaScript。官方提供了 .deb.rpm 可供安装。我在 Arch Linux 上安装的时候,本来直接用的 aur/grafana,后来发现 go get 和 npm install 的过程简直蛋疼,于是将官方提供的 .tar.gz 二进制包做了一份 aur/grafana-bin,直接装这个就省力多了。

安装之后启动服务,在浏览器中打开 :3000 端口,即可用 admin / admin 登录。在 grafana.ini 中也可以开启匿名登录。

登录后需要先添加数据源(data source),将 graphite-api 的地址和端口(默认是 8888)填写进去即可。

然后便可以开始画图了,通过简单的网页操作即可添加各种不同类型的图表,也可以方便地选用 Graphite 内置的各种函数。Grafana 的具体操作可以从官方文档了解到,这里不再赘述。

以下是我用 Grafana 绘制的网络质量监控的页面,其中用到 TemplatingSinglestat Panel 等功能:

grafana-dashboard-network-fullscreen-20160319-2-blurred

请原谅那一段 100% 丢包率的部分,是我的一台 VPS 所在的机房出了问题……

五、监控更多的指标

collectd 还可以做很多事情,只用它的 ping 插件太大材小用了。玩熟了 ping 插件之后,我又用它监控了局域网内各机器(自己的笔记本、Gen8 上运行的其他虚拟机等)的 CPU、内存、磁盘、网络等其他指标。collectd 的客户端也是移植性很强,我甚至在 Raspberry Pi 上也部署了一下。Windows 机器的话,则可以安装 SSC Serv,这是一个协议兼容的 collectd agent,免费版本有五分钟上报一次的限制,基本够用了。

HP ProLiant MicroServer Gen8 上手玩

曾经,一台共享空间的 PHP 「虚拟主机」在大部分人眼里就是很高级的「服务器」了,后来随着虚拟化技术的发展,OpenVZ, Xen, KVM 虚拟机越来越普及,Linode 和 DigitalOcean 更是将 $10/mo 的廉价 VPS 推广开来,VPS 可以做很多 PHP 共享主机做不到的事情。近两年,随着越来越多中小企业的发展,「微型服务器」的概念也慢慢出现了。HP ProLiant MicroServer Gen8 就是其中的一员。最近我也入手了一台,当家庭服务器使用。

c03760124

一、购买

Gen8 是 2013 年的产品了,现在的售价已经比刚出来时便宜不少了。爱折腾的可以去海淘一个便宜的版本,我为了省事就直接在□东上购买了。□东上提供了三个型号,分别是:

  • Intel G1610 CPU + 2 GB RAM
  • Intel G2020 CPU + 2 GB RAM
  • Intel E3-1220Lv2 CPU + 4 GB RAM

主要的区别就是在 CPU 了,三款 CPU 分别是赛扬(Celeron)、奔腾(Pentium)和至强(Xeon)系列的产品,价格也由低到高。然而这内存实在是令人郁闷,两条插槽明明可以可以最高 16 GB(甚至单条 16 GB 的话可以最高 32 GB,兼容性未知),但最高配置也就 4 GB 是什么鬼……考虑了一下自己主要是用来做 NAS 使用,但是同时又有虚拟化的需求,因此决定购买赛扬型号的,然后自己加内存。

最终我的购买清单是:

  • HP ProLiant Gen8, Intel G1610 CPU, 2 GB RAM
  • Kingston DDR3 1600 ECC RAM, 8 GB
  • Samsung 850 EVO SSD, 120 GB
  • WD Red NAS HDD, 2 TB
  • 六类网线,3 米 * 3

Gen8 一共四个 SATA 盘位外加一个光驱位,因此有心折腾的话最多可以扩展成五块硬盘。我按照我目前的需求就一块 SSD 一块 HDD 搞定了。网线我买了三根,是因为 Gen8 有三个 RJ-45 接口,两个是主网卡上的,另一个是 iLO 的管理接口。在 iLO 中也可以设置让 iLO 和主网卡共享网络接口。

二、开箱及组装

以上内容合计约 3700 人民币,下单后当天下午就送到了。以下多图杀猫,点击可看大图。

在装硬盘的时候我踩坑了。由于之前对 Gen8 的 SATA 背板不了解,想当然地买了块普通的 2.5 英寸转 3.5 英寸硬盘支架,想要把 SSD 装到 1 号槽、HDD 装到 2 号槽的时候发现 2.5 英寸的 SSD 套个普通的 3.5 英寸支架并不能插进硬盘槽里……于是最后的解决方案是把 1 号槽的背板拆下来,拉到顶上,把 SSD 塞在那里,于是最后把 SSD 放顶上了。好在那儿本来就是光驱的位置,所以并不影响盖上盖子。

另一种方案是把 SSD 接到闲置的光驱线上,这样下面四个盘位都可以插满大容量 HDD。这种方案需要自己购买 SATA 转接线。

在装好硬盘之后,需要在 BIOS 里配置 B120i RAID 控制器。说是 RAID 控制器,其实是个伪 RAID,加之我并没有 RAID 需求,我直接禁用掉了,这样操作系统看到的就是个普通的 SATA 控制器,能直接访问到下面挂载的硬盘。

三、搭建 ESXi 虚拟化环境

Gen8 作为一个微型服务器,定位于中小企业和家庭,配置远不如它那些安身于 IDC 里的同侪们,但是用作网络存储却是非常合适。四盘位使用 3 TB 硬盘可提供 12 TB 的存储空间;如果使用那些 6 TB 的硬盘的话,更可扩展至 24 TB 存储空间。我入手 Gen8 也是想作为网络存储的,但是同时又想一机多用,跑几个虚拟机玩玩。因此需要一套虚拟化方案。

虚拟化方案选择

纳入我考虑范围的有 VMware ESXiXenServerlibvirt + QEMU 这三种方案。综合我自己的需求、自身的兴趣(后两者都已经玩过了,第一个还没玩过),以及 Twitter 的投票,我选择了 VMware ESXi 作为虚拟化方案。

ESXi 的前身是 ESX,是 VMware 出品的优秀的 bare metal 虚拟化方案,在 VMware 网站上可以免费注册得到一份授权,可无限期使用。与 VirtualBox、QEMU、Hyper-V 等方案不同,ESXi 是 Type-1 Hypervisor,它直接运行于裸机上,不需要先安装一个操作系统,因此性能开销小,本身占地也很小,很适合安装到 TF 卡或 U 盘等小型存储器中。正巧,Gen8 的机箱内主板上有一个 USB 和一个 TF 卡接口,可以把 ESXi 安装进去。由于 ESXi 启动完成之后就在内存中运行了,不用担心 U 盘和 TF 卡的读写性能问题。

安装并连接到 ESXi

在 VMware 网站上可以下载到 ESXi 6 的安装镜像,但是 HP 为 Gen8 专门制作了一个定制版的 ESXi 镜像,自带了 Gen8 的驱动程序,建议使用 HP 版本包括我在内已经有两位用户发现 HP 定制版 ESXi 与 RDM 有兼容性问题,会导致 ESXi 虚拟机卡死。使用原版 ESXi 镜像无兼容性问题。安装镜像约 380 MiB,局域网内网络挂载安装也不会太慢,直接使用 iLO 的虚拟光驱挂载功能即可。注意,默认的 iLO 版本并不支持虚拟光驱功能,需要去 HP 那里申请一个 60 天 iLO Advanced 试用版授权。或者你可以本地起个一个 HTTP 服务器,然后使用 iLO 免费版里的从 URL 加载镜像的功能安装。

我不知道原版 ESXi 安装过程是什么样的,但是 HP 为 Gen8 定制的 ESXi 安装镜像在整个安装过程中除了问了我安装到哪里,其他什么也没问。我自然是选择安装到 U 盘了(记得提前插好)。安装完成后可以看到上灰下黄的屏幕上显示着 ESXi 通过 DHCP 自动获取的 IP 地址,用浏览器访问即可看到欢迎页面,然后……你几乎什么也不能做,不过你可以顺着欢迎页面上的链接去下载个 vSphere Client。

这是令我比较郁闷的部分。通过这几天的使用,我发现 ESXi 是个好东西,但是它提供的连接方式却不怎么样。目前你有四种方式去管理它上面的虚拟主机:

  • 使用 vSphere Client —— 这是一个 Windows-only 的客户端;
  • 使用 vSphere Web Client —— 你需要部署 vCenter,对于只跑一个 ESXi 实例的用户来说太不值得了;
  • 使用 vSphere CLI —— 也依赖 vCenter;
  • 安装 ESXi Web UI —— 似乎是个 2016 年新出的试验产品,功能还不完善,bug 也多。

作为一个 Arch Linux 用户,我自然想免客户端直接从浏览器里管理虚拟机了,但是安装并使用了 ESXi Web UI 之后我发现它实在是初级了一些,于是最后还是屈服于 VirtualBox + Windows + vSphere Client 了。

连接到 ESXi 之后需要先创建数据存储(Datastore)。前面说过,我的 Gen8 主板上插了个 16 GB 的 U 盘(ESXi 就装在里面),硬盘位插了一块 SSD 和一块 HDD,我直接把整块 SSD 都添加到 ESXi 里作为 Datastore 了。ESXi 会将其分区并格式化成 VMFS 作为虚拟机存储。剩下的 HDD 则通过 RDM 分配给虚拟机使用。

创建磁盘映射(RDM)

vSphere Client 的 GUI 还是不错的,功能强大、操作简单,创建虚拟机的过程不再详述。下面讲讲 RDM。

前面说过,NAS 将是 Gen8 的重要用途,因此大容量硬盘是少不了的,但是我的 NAS 是跑在虚拟机里的,考虑到数据迁移和恢复的便利性,最好能让虚拟机直接访问到物理磁盘,而不是再隔一层文件系统。让虚拟机直接访问到物理机的磁盘有两种思路:Passthrough 和 RDM (Raw Disk Mapping)。前者需要 CPU 支持 VT-d,并且按照 @Orz_C 的实验,似乎只能把整个 SATA 控制器(连同下面的四个盘位)一起直通进去,不能单独传递下面挂载的磁盘。很遗憾,我的赛扬 CPU 虽然支持 VT-x 却不支持 VT-d,并且我的 SATA 控制器下面挂的并不都是想传递给虚拟机的硬盘,因此此路不通。

那就用 RDM 吧。RDM 其实是 VMDK(VMware 开发的开放虚拟磁盘格式)的功能,原理是创建一个(几乎不占空间)的特殊 .vmdk 文件映射到一块物理磁盘,当虚拟机向这块 VMDK 写入时,实际写入的是后面的物理磁盘。在 VirtualBox 里也可以用这种方法让虚拟机直接使用物理磁盘。ESXi 支持在 GUI 中创建到 LUN 的 RDM,却必须要借助命令行才能创建到本地 SATA 磁盘的 RDM。

在 ESXi 的设置中打开 SSH 访问,用 SSH 登录之后看到 /dev/disks/ 底下有 Gen8 上所有的磁盘:

[root@ezsetupsystem3ca82a9fd134:~] ls -lh /dev/disks/
total 4160150040
-rw------- 1 root root 14.4G Mar 13 16:29 mpx.vmhba32:C0:T0:L0
-rw------- 1 root root 4.0M Mar 13 16:29 mpx.vmhba32:C0:T0:L0:1
-rw------- 1 root root 250.0M Mar 13 16:29 mpx.vmhba32:C0:T0:L0:5
-rw------- 1 root root 250.0M Mar 13 16:29 mpx.vmhba32:C0:T0:L0:6
-rw------- 1 root root 110.0M Mar 13 16:29 mpx.vmhba32:C0:T0:L0:7
-rw------- 1 root root 286.0M Mar 13 16:29 mpx.vmhba32:C0:T0:L0:8
-rw------- 1 root root 2.5G Mar 13 16:29 mpx.vmhba32:C0:T0:L0:9
-rw------- 1 root root 111.8G Mar 13 16:29 t10.ATA_____Samsung_SSD_850_EVO_120GB_______________S21XXXX_____
-rw------- 1 root root 111.8G Mar 13 16:29 t10.ATA_____Samsung_SSD_850_EVO_120GB_______________S21XXXX_____:1
-rw------- 1 root root 1.8T Mar 13 16:29 t10.ATA_____WDC_WD20XXXX_________________________WD2DXXXX
-rw------- 1 root root 1.8T Mar 13 16:29 t10.ATA_____WDC_WD20XXXX_________________________WD2DXXXX:1
lrwxrwxrwx 1 root root 20 Mar 13 16:29 vml.0000000000766d68626133323a303a30 -> mpx.vmhba32:C0:T0:L0
lrwxrwxrwx 1 root root 22 Mar 13 16:29 vml.0000000000766d68626133323a303a30:1 -> mpx.vmhba32:C0:T0:L0:1
lrwxrwxrwx 1 root root 22 Mar 13 16:29 vml.0000000000766d68626133323a303a30:5 -> mpx.vmhba32:C0:T0:L0:5
lrwxrwxrwx 1 root root 22 Mar 13 16:29 vml.0000000000766d68626133323a303a30:6 -> mpx.vmhba32:C0:T0:L0:6
lrwxrwxrwx 1 root root 22 Mar 13 16:29 vml.0000000000766d68626133323a303a30:7 -> mpx.vmhba32:C0:T0:L0:7
lrwxrwxrwx 1 root root 22 Mar 13 16:29 vml.0000000000766d68626133323a303a30:8 -> mpx.vmhba32:C0:T0:L0:8
lrwxrwxrwx 1 root root 22 Mar 13 16:29 vml.0000000000766d68626133323a303a30:9 -> mpx.vmhba32:C0:T0:L0:9
lrwxrwxrwx 1 root root 74 Mar 13 16:29 vml.0100000000202020202057442d574343344d30465a36374831574443205744 -> t10.ATA_____WDC_WD20XXXX_________________________WD2DXXXX
lrwxrwxrwx 1 root root 76 Mar 13 16:29 vml.0100000000202020202057442d574343344d30465a36374831574443205744:1 -> t10.ATA_____WDC_WD20XXXX_________________________WD2DXXXX:1
lrwxrwxrwx 1 root root 72 Mar 13 16:29 vml.0100000000533231564e58414831373636383944202020202053616d73756e -> t10.ATA_____Samsung_SSD_850_EVO_120GB_______________S21XXXX_____
lrwxrwxrwx 1 root root 74 Mar 13 16:29 vml.0100000000533231564e58414831373636383944202020202053616d73756e:1 -> t10.ATA_____Samsung_SSD_850_EVO_120GB_______________S21XXXX_____:1

那些 mpx 开头的是那只 16 GB 的 U 盘及其分区,四个 t10 开头的代表 SSD 和 HDD,剩下的那些是指向前面那些的符号链接。

这里我选择 WDC 的那个块设备(即我的 WD 硬盘),创建 RDM:

vmkfstools -r /dev/disks/XXXX /vmfs/volumes/EVO/WDYYYY.vmdk

第一个参数是代表物理磁盘的块设备(注意不带冒号,即整块硬盘,而不是其中的分区),第二个参数是创建的 .vmdk 文件的路径,我这里保存到上一节创建的名为 EVO 的 Datastore(即我的 SSD)根目录下名为 WDYYYY.vmdk 的文件(我是拿硬盘序列号作为文件名的)。

另外,-r 参数创建的是 Virtual Compabilitiy Mode RDM,即 ESXi 会截获除 READ / WRITE 之外所有 SATA 指令;如果换成 -z 参数,则是创建 Physical Compability Mode RDM,即 ESXi 除了 LUN REPORT 指令,其他全部原样传递给物理磁盘。没啥特殊需求用 -r 即可。两种模式只有在 hdparm 等涉及到磁盘本身参数的命令才会有区别,数据层面没有区别。

将这样创建得到的特殊 .vmdk 文件分配给虚拟机,便可在虚拟机中访问到外层 ESXi 的硬盘。

四、搭建 NFS 和 Samba

接下来需要将硬盘中的文件分享给其他主机。如果你的网络里只有 Linux 主机的话,那么搭个 NFS 就足够了。初版 NFS 于 1984 年由 Sun 研发,几十年来久经考验,十分适合局域网内文件共享。

在 Linux 发行版中,NFS 服务通常由 nfs-utils 包提供,安装后修改 /etc/exports 文件,将硬盘挂载点共享出去即可。比如这是我的配置:

/media/disk1/public 192.168.2.0/24(ro,sync,no_subtree_check,no_root_squash)
/media/disk1/incoming 192.168.2.0/24(rw,sync,no_subtree_check)

共享了 public 和 incoming 两个目录,前者允许局域网内读写操作,后者则是只读。修改此文件用后可以用 exportfs -rav 命令重载。注意,no_root_squash 使得拥有 NFS 客户机 root 权限的人也可以 root 身份对 NFS 服务器的共享目录进行操作(与 MooseFS 的默认行为一致),如果不需要这一特性可以关闭。

在我的 Linux 笔记本上,可将 NFS 挂载项写个 /etc/fstab 实现开机自动挂载(voila 是文件共享服务器的主机名):

# NFS @ voila
voila:/media/disk1 /nfs/voila nfs4 rsize=8192,wsize=8192,timeo=14,_netdev 0 0

因为我还需要向 Windows 用户分享文件,因此我还装了一个 Samba。Samba 的配置比 NFS 复杂多了,以下是我用的最简配置:

# /etc/samba/smb.conf
[global]
workgroup = WORKGROUP
server string = Voila Samba Server
hosts allow = 192.168.2. 127.
log file = /var/log/samba/%m.log
max log size = 50
security = user
map to guest = Bad User

[public]
comment = Public Read-only
path = /media/disk1/public
public = yes
browseable = yes
read only = yes

[incoming]
comment = Public Read-write
path = /media/disk1/incoming
public = yes
only guest = yes
browseable = yes
read only = no
writable = yes

启动 Samba 服务后,列在 hosts allow 里的主机即可无密码访问 Samba 共享。Windows 用户可以将 Samba 目录映射为网络驱动器,方便日后访问。

五、家庭服务器的其他用途

本节列举一些我能想到家庭服务器的其他用途,可能是我已经实现的、正在实现的、想要实现的,以及暂时不想实现的。排名不分先后,仅供参考:

六、致谢

以下推友作为 Gen8 的先驱玩家,在本文作者折腾 Gen8 的过程中给予了诸多帮助和指导,特此感谢(排名由 sort 提供):

使用 nghttpx 搭建 HTTP/2 代理

HTTP/1.1,定义于 1999 年,至今仍在流行。纵使人们试图在它上面添加各种黑科技,但它依然有各种各样的不足。终于,在 2015 年 5 月,HTTP/2 发布了。HTTP/2 基于 SPDY 而建,性能和特性较 HTTP/1.1 有了极大的提升,此外,虽然 HTTP/2 标准本身并没有强制 TLS 加密(HTTPS),但主流实现(Google Chrome, Mozilla Firefox)均要求 HTTP/2 被包裹在 TLS 中,因此,HTTP/2 + TLS(HTTPS)已是事实上的标准

本文中,如无特殊说明,「HTTPS」指代「HTTP + TLS」,其中的 HTTP 可以是 HTTP/1.1, SPDY/3.1 或 HTTP/2;但由于几乎所有的 HTTP/2 实现全部要求 TLS,因此单说「HTTP/2」的话,一般指自带了 TLS 的 HTTP/2。

本文介绍使用 nghttpx 配合 Squid 搭建一个支持 HTTP/2 的 HTTPS 代理的方法。

nghttpx 本身并不是一个代理,它只是一个翻译器,因此如果我们需要一个支持 HTTP/2 的正向 HTTPS 代理,可以用一个 HTTP/1.1 的正向代理(如 Squid)和 nghttpx 接在一起实现。使用这样一个 HTTPS 代理,既可以享受 HTTP/2 对多连接的优化(提高客户端和代理服务器之间的连接流畅度),又可以享受外层 TLS 带来的加密和安全。且由于流量特征是 HTTPS,不仅额外开销小,而且在一些封锁严重的 ISP 里也能应用自如。(如封锁了 DTLS 流量的情况下,OpenConnect / AnyConnect 只能 TCP over TCP,效率很低)

一、需求

需求有两种,一种是客户端原生支持 HTTP/2 的,以下以 Chrome 为例:

+------------+    +------------+    +------------+    +------------+                  
|            |    |            |    |            |    |            |                  
|   Chrome   +----+  nghttpx   +----+   Squid    +----+  Internet  |                  
|            |    |            |    |            |    |            |                  
+------------+    +------------+    +------------+    +------------+                  

如图,nghttpx 与 Squid 部署于服务器上,客户端的 Chrome 与 nghttpx 用 HTTP/2 交流,nghttpx 将请求翻译成 HTTP/1.1 发给 Squid,最后 Squid 抓取了结果返回。

另一种是客户端不支持 TLS 的,以下以 Pidgin 为例:

+------------+    +------------+    +------------+    +------------+    +------------+
|            |    |            |    |            |    |            |    |            |
|   Pidgin   +----+  nghttpx   +----+  nghttpx   +----+   Squid    +----+  Internet  |
|            |    |            |    |            |    |            |    |            |
+------------+    +------------+    +------------+    +------------+    +------------+

如图,Pidgin 将 HTTP/1.1 请求发给本机的 nghttpx,本机的 nghttpx 翻译成 HTTP/2 之后发给服务器上的 nghttpx。之后的过程和上一种相同。

二、工具

nghttp2 是一个很优秀的 HTTP/2 的 C 类实现。它的前身是 SPDY 库 spdylay,作者都是 Tatsuhiro Tsujikawa,同时他也是著名下载工具 Aria2 的作者。nghttp2 含有多个组件,其中的 nghttpx 程序,可以进行 HTTP/2 和 HTTP/1.1 之间的翻译,如果编译时链接了 spdylay,它也可以支持 SPDY/3.1。

如果你是 Arch Linux 用户,可以直接使用我维护aur/nghttp2 包,直接 yaourt -S nghttp2 即可,吃豆人会帮你照料好剩下的一切。

如果你是 Debian / Ubuntu 用户,请按照官方 README 完成编译操作,编译完成后在 contrib 目录里可以找到 Upstart 配置文件。注意:nghttp2 库默认是不带 SPDY/3.1 支持的,如果需要 SPDY 支持,请先编译 spdylay 再编译 nghttp2,后者会自动检测到 spdylay 的存在并链接。

如果你是 CentOS 用户,祝您今天有个好心情

三、服务器配置

无论是哪种需求,服务器上都需要 nghttpx 和 Squid。

nghttpx

服务器上 nghttpx,前端接受的是来自客户端的 HTTP/2 请求,后端是 Squid,最小配置是这样:

frontend=0.0.0.0,443;tls
backend=127.0.0.1,3128;no-tls
private-key-file=/path/to/private/key
certificate-file=/path/to/certificate
http2-proxy=yes

其中私钥和证书必须是客户端认可的。你可以选择:

  • NameCheap 之类的网站上买一个商业证书,低至 $9 一年;
  • 自己用 OpenSSL / GnuTLS 等工具签一个,然后在你的客户端里强制设置为信任;
  • 如果你不愿意花钱也不愿意折腾 OpenSSL,那你可以尝试去找家免费的 CA 给你签一个。

需要说明的是,GFW 曾被报道会区分商业证书和野证书并对后者做定点清除。试图使用野证书的同学请将此因素考虑在内。

以上只是最小配置,我个人使用的配置还加上了以下内容,是我在 nghttpx 的文档中挑出来觉得比较有用的选项:

# 使用四个 worker,请根据自己服务器的 CPU 合理调整,太小性能差,太大机器挂
workers=4
# 开启客户端 TLS 认证
verify-client=yes
verify-client-cacert=/path/to/client/ca
# 不添加 X-Forwarded-For 头
add-x-forwarded-for=no
# 不添加 Via 头
no-via=yes
# 不查询 OCSP 服务器
no-ocsp=yes
# 指定 NPN / ALPN 的顺序
#npn-list=spdy/3.1,h2 →这一行已经不用加了,见下
# 只使用 TLS 1.2
tls-proto-list=TLSv1.2
# 只使用 ECDHE 交换(目前性能安全比最优)和 AES 加密,指定 128 位是因为它已经足够安全但性能比 256 位稍优一些
ciphers=ECDHE+AES128
# 开启日志功能
accesslog-file=/var/log/nghttpx/access.log
accesslog-format=$remote_addr [$time_iso8601] "$request" $status $body_bytes_sent $alpn "$http_user_agent"

有关 --npn-list 选项:前文已经说明了,nghttp2 如其名字所示,是一个 HTTP/2 的库,但是由于 Chromium / Google Chrome 的一个 bug(发稿时最新的 v45 仍未修复已在 v46 中修复,Cr 对 HTTP/2 代理的支持有点问题(Firefox nightly 没有问题),而 nghttpx 默认的 NPN / ALPN 顺序是 h2 优先的,所以需要在这里把 spdy/3.1 的优先级调成最高,以便让 Cr 能用 SPDY/3.1 协商……如果它在编译时链接了 spdylay 的话,也能向下支持(已过时的)SPDY。这一选项是调节优先使用哪种协议的。Chrome 46 之后对 HTTP/2 代理的支持已经正常了,这个选项可以不用调了。

有关 --verify-client 功能:请看下文「有关鉴权」一节。

Squid

Squid 是一个久经考验的正向代理。在我们的用例中,它是 nghttpx 的后端,只需监听 localhost 即可。我用的最小配置如下:

http_port 127.0.0.1:3128
http_access allow localhost

# 关闭缓存功能和日志功能
cache deny all
access_log none

# 优先访问 IPv4 站点,有完整 IPv6 支持的机器可以去掉
dns_v4_first on
# 不添加 Via 头
via off
# 删除 X-Forwarded-For 头
forwarded_for delete

我曾试图让 nghttpx 把源 IP 地址发给 Squid 然后让 Squid 记到日志里,但是未能成功,于是 Squid 始终只能记到一堆来自 127.0.0.1 的请求,干脆就把 Squid 的日志关闭,让 nghttpx 去记日志了。其实是可以在 nghttpx 配置里保留 X-Forwarded-For 然后在 Squid 配置里加上 follow_x_forwarded_for allow localhost让 Squid 能记录到原始的来源 IP 地址。感谢 @JmyXu 的指正。

一个可能会让强迫症不爽的地方是,Squid 默认的错误页面会引用 Squid 官网的图片(一只乌贼),而这个图片资源是 http:// 的,因此页面会带有「混合内容」,强迫症用户可以通过编辑 errorpage.css 把这个去掉:

background: url('http://www.squid-cache.org/Artwork/SN.png') no-repeat left;

或者像我一样把这个图片换成 data:image/png;base64 嵌在 CSS 里……

有关鉴权

只按照最小配置来做的话,配置出来的 HTTP/2 代理是没有任何鉴权的,任何人都可以把这个地址填进 Chrome 里当代理用,也就是说,这是一个开放代理。但实践证明:

  • 如果你在公网上搭一个不带 TLS 的 HTTP/1.1 开放代理的话,分分钟各种爬虫就会把你的地址撸走,教你做人;
  • 如果你在公网上搭一个带 TLS 的 HTTP/1.1 开放代理的话,来光顾你的爬虫就非常非常少了,几个月也遇不到几只;
  • 如果你在公网上搭一个只允许 TLS 1.2 的 HTTP/2 开放代理的话,根本不会有爬虫来光顾你……

所以,如果不想弄鉴权的话,问题也不大,因为目前根本没有 TLS 1.2 + HTTP/2 的爬虫,除非你主动把地址告诉别人,否则不会有人来用你的代理。不过,这样毕竟只是迷宫,而不是门锁,所以为了安全还是可以配置一下鉴权。

在这种 TLS 1.2 + HTTP/2 的结构下,鉴权可以在两个阶段做:TLS 和 HTTP(感觉是废话),也就是 nghttpx 和 Squid(好像还是废话)。

在 TLS 层面做鉴权的话,就是用上文所述的 --verify-client 了。你需要自己维护一个 CA,然后把 CA 的根证书放到服务器上,持有该 CA 根证书的私钥签出的证书对应的私钥的用户可以使用该代理,否则根本完成不了 TLS 握手,直接被拒绝。CA 的搭建和管理又是一个巨大的话题了,在此不多做叙述,只是推荐一下两个软件:适用于 GNU/Linux 用户的 XCA,和适用于 OS X 用户的 Keychain。这两个都是能够管理中小型 CA 的 GUI 程序。我个人使用的则是 EasyRSA。当然如果你足够硬核,也可以直接使用命令行的 OpenSSL 去管理 CA。再次强调,这个 CA 只是客户端认证所用的,和你买证书的那种商业 CA 没有也不应该有联系

在 HTTP 层面做鉴权的话,请照着 Squid 官方文档做。

推荐用 TLS 鉴权,你会爱上它的。而且 TLS 鉴权的话,Chrome 能用 AutoSelectCertificateForUrls 策略自动选证书,不用每次开 Chrome 的时候点一下。

四、客户端配置

客户端配置分为两种。Chrome 和 Firefox 等直接支持 HTTP/2 代理的,直接填进去就行。大部分不支持 TLS 的程序,需要在本地再起一个 nghttpx,翻译一下,在本地生成一个 HTTP/1.1 的代理,供程序使用。

无需翻译的程序

Chromium / Google Chrome 理论上支持 HTTPS 代理的,但是如上文所述,目前由于一个 bug 的存在,对 HTTP/2 代理的支持有问题,暂时只能用 SPDY/3.1;Firefox 曾经不能正常使用 HTTPS 代理(当年 Chrome 是唯一能使用 HTTPS 代理的浏览器),现在它的 nightly 版本反而是支持 HTTP/2 了而超越了 Cr……(bug 已经修复)

令人郁闷的是,无论是 Cr 还是 Fx,都不能方便地通过 GUI 配置 HTTPS 代理,只能通过命令行或插件的方式来使用 HTTPS 代理。比如这样一个 pac 文件便可以让 Cr 和 Fx 使用 HTTPS 代理了:

function FindProxyForURL(url, host) {
  return "HTTPS proxy.example.org:443";
}

当然,pac 文件可以写得非常复杂,也可以使用浏览器插件进行更灵活的代理配置。Cr 用户推荐使用 SwitchyOmega

需要翻译的程序

如前文所述,目前除了 Cr 和 Fx,大部分软件是不支持 HTTP/2 的,而 nghttpx 是个 HTTP/1.1 和 HTTP/2 的翻译器,因此我们可以在本机起一个 nghttpx 生成一个 HTTP/1.1 的代理供不支持 HTTP/2 的程序使用。这种情况下,nghttpx 的前端接收 HTTP/1.1 的请求,然后翻译成 HTTP/2 发给服务器上的另一个 nghttpx 实例。最小配置如下:

frontend=127.0.0.1,8080;no-tls
backend=proxy.example.org,443;proto=h2;tls
http2-proxy=yes

同样地,这只是最小配置,我个人使用的配置中还有以下选项:

# 认证用证书和私钥,如果你没用 TLS 认证则不需要
client-cert-file=/path/to/certificate
client-private-key-file=/path/to/private/key
# 四个 worker,请根据自己计算机/手机性能调整
workers=4
# 不添加 X-Forwarded-For 头
add-x-forwarded-for=no
# 不添加 Via 头
no-via=yes
# 不查询 OCSP
no-ocsp=yes
# NPN / ALPN 优先使用 h2
npn-list=h2

另外可能有用的选项是 -k。如果你用了野证书,这个选项让 nghttpx 放弃证书校验(不安全!),此外它在手机等慢速网络下也有缩短启次握手时间的效果。另外如果你想看实时请求情况的话,加上 -L INFO 能看到漂亮的彩色输出。这个 nghttpx 跑起来之后,别的程序设置 http://127.0.0.1:8080 为代理即可使用。

刚才提到了手机。是的,手机。Tatsuhiro Tsujikawa 大大的程序都是为 Android 交叉编译优化过的。(什么,您是 iOS 用户?您还是用您的 APN 代理,也就是 HTTP 明文代理吧……)

在 nghttp2 的文档中,提供了两种方便的交叉编译 Android 版 nghttpx 的方法,一种是自己装 Android NDK 和依赖,然后用 android-configandroid-make 脚本自动做。另一种是用 Dockerfile.android 文件,在 Docker 容器里装上乱七八糟的编译环境和依赖,最后产出珍贵的 nghttpx 文件并复制到容器外面来。编译完的二进制文件记得 strip 一下,能从 11 MiB 减到 2 MiB……

在 Android 上运行起 nghttpx 之后(可以用 JuiceSSH 之类的起一个,无需 root),推荐配合 Drony 使用(也无需 root),该应用使用 VpnService() 捕获所有应用流量,然后再按照你定的规则(来源地址、应用名、目标主机名、目标端口号、HTTP 方法等),将这些流量进行分流(直连、截断、传给代理、交给 pac 处理等)。

五、尾声

我竟然已经整整一年没有写博客了。一年里可以写的东西其实不少,但是由于各种原因的确没怎么写。今天因某人提醒我一年没更新了,又正好手头的事情告一段落,于是便这么写了一篇。也算是能造福一些人吧。

最后,Google Chrome 使用 HTTP/2 代理看 YouTube 4k 效果如下:

youtube-4k-nghttpx

本文原载于: https://wzyboy.im/post/1052.html 。如有转载请注明。

使用国外 DNS 造成国内网站访问慢的解决方法

你是否是一个使用国外 DNS 的中国网民?你是否发现使用国外 DNS 之后访问某些国内网站奇慢无比?这不是 DNS 慢,而是电信到联通的线路太慢。如果你愿意小小地折腾一下,那么跟随本文,你可以解决这一问题。

一、为什么要用国外 DNS

由于众所周知的问题,国内 DNS 服务器解析国外网站会遭到 DNS 污染和投毒,使之解析到完全虚构的 IP 上,造成「开了 VPN 也没法访问 Twitter 或 Facebook」等问题。以下是一个例子:

wzyboy@vermilion:~$ dig twitter.com @8.8.8.8 +short
199.59.148.82
199.59.149.230
199.59.148.10
wzyboy@vermilion:~$ dig twitter.com @221.228.255.1 +short
93.46.8.89

Twitter 正确的 IP 地址应该是 199.59.148.0/24 里的那几个,但是如果用 221.228.255.1 这台中国电信的 DNS 服务器查询,查到的就是不知道什么鬼地址了,地理信息是在意大利,乱七八糟的。正是因为这样的 DNS 解析不正确的情况出现,不少人转而使用了国外的 DNS 服务器,如老牌的 OpenDNS 以及这几年新崛起的好记又好用的 Google Pulic DNS 即 8.8.8.8 和 8.8.4.4。使用它们进行查询,再配合以 VPN 或者浏览器的远程 DNS 解析,便可避免 DNS 污染的情况出现,从而解析出正确的地址。

此外,拒绝使用电信的 DNS 服务器,还可以避免烦人的「114 上网导航」页面……

二、为什么使用国外 DNS 会「慢」

我是在「慢」上加了引号的,因为这其实不是国外 DNS 慢,而是你要访问的网站的 CDN 分配错误,慢。由于国内各大运营商之间的主干线路带宽太窄,所以导致「最远的距离是从电信到联通」,电信用户访问联通的服务器非常慢,联通用户访问电信的服务器也非常慢,相信这都是大家有体验的。因此,国内不少网站都用了双线 CDN,在电信的机房里放点服务器,再在联通的机房里放点服务器。运用智能 DNS 技术,当你访问网站的时候,DNS 根据你的来源 IP 判断你是电信用户还是联通用户,然后再返回相应的 IP 地址,这样你会访问到就近的、同运营商的服务器,访问速度就大大提升了。而如果使用国外的 DNS 的话,你的查询来源来自国外,国内网站的 DNS 无法判断你是电信用户还是联通用户,就胡乱分配你一个服务器。比如我是一个江苏电信的用户,但当我访问淘宝网的时候,淘宝的 DNS 把我解析到青岛联通的服务器上,奇慢无比。所以:

很多人认为 Google Public DNS, OpenDNS 等「慢」,主要不是查询慢,而是电信到联通之间太慢。

当然了,如果硬要比较查询的话,倒也是会慢很少一点的:

;; 使用 8.8.8.8 解析 www.google.com 耗时 79 毫秒
;; Query time: 79 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Sep 6 17:20:37 2012
;; MSG SIZE rcvd: 143
;; 使用中国电信 221.228.255.1 服务器解析 www.google.com 耗时 6 毫秒
;; Query time: 6 msec
;; SERVER: 221.228.255.1#53(221.228.255.1)
;; WHEN: Thu Sep 6 17:20:44 2012
;; MSG SIZE rcvd: 284

别看 6 毫秒和 79 毫秒差别很大的样子,但是人类是很难感觉出来的,而且,这只是查询时间,与实际的访问速度无关,就算你一整天都在刷 www.google.com,也就每小时慢个几百毫秒的样子,根本感觉不出来。真正慢的原因,还是上文所说的「电信到联通」的问题。

三、问题的解决思路

现在问题明确了:使用国外 DNS 之后,查询来源变成国外的 IP,使用了 CDN 加速的国内网站的 DNS 会无法判断你的来源,胡乱给你分配一个地址,如果不是同一个运营商的,访问速度便会很慢。

那解决方案也就出现了:让国内网站的 DNS 服务器知晓你的来源,从而给你分配正确的服务器 IP。于是 Google 起草了一个专有协议,叫 EDNS,在 DNS 查询请求中包含源地址,这样淘宝就知道查询来源不是 Google 服务器,而是电信的某用户,就不会把你扔到联通服务器上了 。听起来很美好是吧?不过这个协议不开放,目前几乎没有人用,所以,问题丝毫没有解决。

新思路是:访问那些会因 CDN 加速解析错误而极其缓慢国内网站的时候,直接向国内的服务器发送请求,让 DNS 知晓你的来源,给你分配个正确的 IP。访问其他网站的时候,再通过国外的 DNS 查询。

听起来很简单的样子,实现起来也不难:用 dnsmasq 在本地搭个 DNS 缓存服务器,规定哪些域名用哪个服务器查就好了。

四、安装及配置 dnsmasq

安装 dnsmasq

dnsmasq 是一个非常轻量的 DNS 缓存及 DHCP 服务器,在我的 Arch Linux 上只占用了 368 KiB 的磁盘空间,相比功能极其强大的 BIND9 来说小多了(BIND9 的安装体积是 6.23 MiB)。不光是体积小,它的功能也很专一,配置起来也是十分方便的,五分钟就可以搞定。

Ubuntu 12.04 及之后的版本应该自带了 dnsmasq。如果没有,可以使用 sudo apt-get install dnsmasq 安装。Arch Linux 直接 sudo pacman -S dnsmasq 即可。我的笔记本电脑需要给手机 DHCP 及 IP 转发用,因此早就安装了 dnsmasq,但是直到今天才想起这么用它……

配置 dnsmasq

dnsmasq 的各参数可以通过 man dnsmasq 查看,配置文件中也有许多清晰明了的注释。默认的配置文件位于 /etc/dnsmasq.conf,打开它,可以更改这几个地方:

no-resolv
no-poll
server=8.8.8.8
server=8.8.4.4
server=/cn/114.114.114.114
server=/taobao.com/114.114.114.114
server=/taobaocdn.com/114.114.114.114
server=/tbcache.com/114.114.114.114
server=/tdimg.com/114.114.114.114

第一行的 no-resolv 和第二行的 no-pull 让 dnsmasq 不要通过 /etc/resolv.conf 确定上游服务器,也不要检测 /etc/resolv.conf 的变化(因为我们就是要拿本机当服务器嘛),接下来两行指定 dnsmasq 默认查询的上游服务器,此处以 Google Public DNS 为例,喜欢用 OpenDNS 的也可以改 OpenDNS。接下来就是规定一张名单了,把一些国内网站的域名写在这里即可。比如 server=/cn/114.114.114.114 便是把所有 .cn 的域名全部通过 114.114.114.114 这台国内 DNS 服务器来解析,你也可以改成其他的国内 DNS 比如你的运营商提供的 DNS。接下来四行是淘宝的几个域名,让它们也通过国内的 DNS 服务器解析。

这样分流操作之后,默认所有域名都通过 8.8.8.8 和 8.8.4.4 解析,但是所有的 .cn 域名以及淘宝的域名通过 114.114.114.114 这台国内 DNS 服务器解析。当然,除了淘宝网,你也可以添加更多的域名,根据自己的喜好,把经常访问,但是使用国外服务器解析到很慢的服务器上的网站域名都可以添加进去,不过:如果这个网站本身就只能一台服务器,没有 CDN 加速,那再怎么添加也是无济于事的,得让网站管理员去买双线机房……另外,在写这篇文章的时候,发现 @felixonmars 维护了一张国内常用的、但是通过国外 DNS 会解析错误的网站域名的列表,据他所,这是他在公司里部署这一套东西之后,公司里其他人报怨「慢」的网站域名收集来的,应该囊括了绝大多数有此问题的网站,值得依赖,欢迎选用。如果觉得直接把这么多域名加在 /etc/dnsmasq.conf 里不爽的话,可以在 dnsmasq.conf 里把 conf-dir=/etc/dnsmasq.d 这一行取消注释,然后把在 /etc/dnsmasq.d 里弄点列表。比如我就是把 @felixonmars 维护的列表放在 /etc/dnsmasq.d/server.china.conf 里。

配置好之后,保存,Ubuntu 可用 sudo service dnsmasq restart,Arch Linux 可用 sudo rc.d restart dnsmasq 重启 dnsmasq。如果没有错误的话,这时本地的 dnsmasq 已经跑起来了。

测试 dnsmasq

测试一下:

wzyboy@vermilion:~$ dig www.taobao.com @8.8.8.8 +short
www.gslb.taobao.com.danuoyi.tbcache.com.
scorpio.danuoyi.tbcache.com.
119.167.195.251 → 这是淘宝的青岛联通的服务器,我用江苏电信连奇慢无比
119.167.195.241 → 这也是青岛联通
wzyboy@vermilion:~$ dig www.taobao.com @127.0.0.1 +short
www.gslb.taobao.com.danuoyi.tbcache.com.
scorpio.danuoyi.tbcache.com.
222.186.49.251 → 解析到常州电信了,快!
61.155.221.241 → 这是上海电信
wzyboy@vermilion:~$ dig twitter.com @127.0.0.1 +short
199.59.150.7 → Twitter 还是用 8.8.8.8 解析的,所以解析出来是未经污染的正确地址
199.59.148.82
199.59.149.230

效果很明显,这时可以把本地的 DNS 设为 127.0.0.1 了。大部分 Linux 用户直接更改 /etc/resolv.conf 的内容为

nameserver 127.0.0.1

即可。Ubuntu 12.04 及以后的用户,可能需要对 resolv.conf 做一些手脚,具体参考这里。如果不愿意改的话,可能每次联网都要手工改一次 resolv.conf,或者在 NetworkManager 中手工指定 127.0.0.1 为 DNS。DHCP 用户的话,可以通过 /etc/resolv.conf.head 之类的文件来保证 127.0.0.1 在第一行。

这样配置完之后,如果你没有在 dnsmasq 里限定查询 IP,那么你的家人、朋友们也是可以把你的电脑作为 DNS 服务器的。如果你本地是 VPN 全局翻墙的话,需要把你选择的国内 DNS 服务器通过路由表加入直连的范围内。当然,已经使用 chnroutes 的就不需要了。

五、我不是 Linux 用户怎么办?

如果你是 Windows 用户

参考这个问题,去配个 BIND9 的 Windows 版本试试吧。或者可以装个 dnsmasq 的 Windows 代替品。
更新:有人写了篇《Windows 下使用国外 DNS 访问国内网站慢的解决方法》,使用 Windows 下的同类软件解决此问题,值得阅读。

如果你是 Mac OS X 用户

Mac OS X 的 resolver 比较独特,似乎有比较简陋的解决方法,参考这篇文章。另外,OS X 似乎也是自带 named 的,所以……
安装 Homebrew 吧,然后 brew install dnsmasq 喽……

其实还有更通用的方法

听说过 VirtualBox 吗?开源、免费、强大的虚拟机软件。可以装个最配置非常低下的虚拟机,比如 32 MiB 内存甚至 16 MiB 内存的虚拟机(要知道 64M 内存的 Linux 已经可以跑 WordPress 这庞然大物了),装个最简单的小 Linux,比如只有 37M 的 Ubuntu Core,然后装上依赖包几乎没有的 dnsmasq,再把 Windows / OS X 的 DNS 设为虚拟机的 IP 地址,于是便可以用了。在当今内存动辄 4 GiB 的情况下,拿 16MiB 内存出来换个更舒畅的上网体验,还是很不错的。

六、尾声

祝各位读者折腾成功,上网愉悦。

补充:其实这样本地 DNS 缓存服务器,还有这样的好处:

wzyboy@vermilion:~$ dig wzyboy.im

; < <>> DiG 9.9.1-P2 < <>> wzyboy.im
;; global options: +cmd
;wzyboy.im.			IN	A
wzyboy.im.		103	IN	A	198.244.51.13
;; Query time: 307 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Sep  6 21:32:07 2012
;; MSG SIZE  rcvd: 54

wzyboy@vermilion:~$ dig wzyboy.im

; < <>> DiG 9.9.1-P2 < <>> wzyboy.im
;; global options: +cmd
;wzyboy.im.			IN	A
wzyboy.im.		95	IN	A	198.244.51.13
;; Query time: 2 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Sep  6 21:32:15 2012
;; MSG SIZE  rcvd: 43

看出来了吧?

本文原载于 wzyboy’s blog,转载请注明本文地址: https://wzyboy.im/post/874.html ,谢谢合作。

进一步部署 Google Authenticator:Apache 模块


Google Authenticator 是个好东西。它不仅可以增强 Google 账户登录的安全性,更因为它开源的特性,被部署到别的地方使用。比如 Linux PAM、WordPress 等,使用户可以借助 Google 的这套 OTP 方案,增强自己的服务器、网站和个人电脑的安全性。笔者之前就写过一篇详细的教程《用 Google Authenticator 加强 VPS 及 WordPress 甚至桌面电脑的安全性》,介绍如何把 Google Authenticator 部署到服务器、WordPress 和个人电脑上。如果你觉得这还不够安全的话,本文将会教会你如何把它部署到 Apache 里。

一、Google Authenticator 的 Apache 模块的优势

如何增强服务器的安全性?这个问题向来都是站长们的讨论焦点。从 SSH 登录方面来说,可以限制 IP 地址登录,可以禁用密码登录(仅用公钥登录),可以用 Fail2ban 来对付它们,甚至可以把尝试暴力破解的 IP 加到 iptables 黑名单中等。但是,Web 前端怎么办?总不能限制 IP 地址访问吧?比如 WordPress 的管理后台和 phpMyAdmin 的登录页面,就是很容易遭到攻击的地方。为了防御这种人肉攻击,简单点地做法是用 mod_auth_basicmod_auth_digest 来进行验证,高档一点的话,可以用 mod_ssl 中的双向 TLS 认证。但是前者太简单,如果页面是 http:// 的话,密码是明文传输的,对攻击者来说十分方便的,而后者相对来说比较复杂,要自己建立并维护一个 CA,或者需要花钱去请第三方 CA 签名证书。而 Google Authenticator 的 Apache 模块 mod_authn_google 则提供一种经济、方便、简洁、有效的认证方式:动态密码认证。

相对 mod_auth_basic 来说,mod_authn_google 的密码是动态的,更不易被猜解;相对 mod_ssl 的双向 TLS 认证来说,mod_authn_google 免去了额外请第三方 CA 签名的资金和复杂的配置 CA 证书的过程的痛苦。

二、安装 mod_authn_google

由于这不是官方模块,所以必然是要我们自己来下载安装的。这个项目的主页在这里,但是它的下载列表中提供的模块是有 Bug 的,如果不想自己修复 Bug 的话,可以在这里下载到 Bug 修复后的 mod_authn_google

注意,这个模块是在 64 位 Linux 下编译的,如果你用的是 32 位的系统,请下载源码后自行编译。

下载得到的是一个 .so 的库文件,可以使用 apxs2 脚本来安装,以 Ubuntu 等基于 APT 和 dpkg 的发行版为例,这个脚本可以通过安装 apache2-prefork-dev 包得到。

sudo apt-get install apache2-prefork-dev

接下来便可使用 apxs2 来安装模块:

sudo apxs2 -i -a -n authn_google mod_authn_google.so

各参数含义:

-i
安装
-a
自动添加 LoadModule 语句,方便加载
-n authn_google
安装后模块的名字
mod_authn_google.so
刚才下载得到的模块的文件名

由于这是一个已经编译好的模块了,所以除了用脚本来自动安装,也可以手工安装:

sudo cp mod_authn_google.so /usr/lib/apache2/modules/
echo "LoadModule authn_google_module /usr/lib/apache2/modules/mod_authn_google.so" | sudo tee /etc/apache2/mods-available/authn_google.load

三、配置 mod_authn_google

首先要建一个供它存放认证信息的地方,比如 /etc/apache2/ga_auth,接下来编辑 /etc/apache2/mods-available/authn_google.conf 文件,以下是范例:

<Directory /secret>				# 要加上验证的目录
Options FollowSymLinks Indexes ExecCGI
AllowOverride All				# 允许每个目录下通过 .htaccess 覆盖这里的全局设置
Order deny,allow
Allow from all

AuthType Basic
AuthName "Secret"				# 弹出窗口的提示信息
AuthBasicProvider "google_authenticator"
Require valid-user
GoogleAuthUserPath ga_auth			# 保存认证信息的目录
GoogleAuthCookieLife 3600			# Cookies 有效时间,这段时间内不用再输密码,单位为秒
GoogleAuthEntryWindow 2				# 当时间不同步时,允许有这样的正负误差。以 30s 为单位
</Directory>

保存退出之后,再

sudo a2enmod authn_google && sudo service apache2 restart

即可,如果没有报错的话,现在现在 mod_authn_google 应该已经在工作了,访问 /secret 的话会提示输入用户名密码,但是我们没有添加用户,所以输啥都是错的。

四、添加认证用户

认证用户是通过 Google Authenticator 提供的工具来生成认证文件的,将生成的认证文件复制到 GoogleAuthUserPath 所对应的目录即可,比如 /etc/apache2/ga_auth

如何使用 Google Authenticator 生成新用户的说明,在这篇文章中已经很清楚了,这里再提一下:

  1. 使用包管理器安装 libpam-google-authenticator 这个包。以 Ubuntu Server 为例:
    sudo apt-get install libpam-google-authenticator
  2. 如果之前用过 Google Authenticator,请先将 ~/.google_authenticator 文件改名备份一下。
  3. 运行
    google-authenticator

    命令,把屏幕上的 QR 码扫描到装有 Google Authenticator 的手机中,并按照提示回答几个问题,生成新的 ~/.google_authenticator 文件。

这样便生成了一个有效的用户。将家目录中新生成的 .google_authenticator 文件复制到 /etc/apache2/ga_auth 目录下,文件名为用户名,并使用

sudo chmod 640 wzyboy && sudo chown root:www-data wzyboy

(把 wzyboy 更换为相应的用户名)更改文件权限,以确保 Apache 能读取它,便完成了一个用户的添加。如需更多用户访问,重复这些步骤即可。最后记得把家目录里的 .google_authenticator 文件改回来。

五、调试与侦错

建议在浏览器的隐身窗口中进行调试以排除 Cookies 的干扰。如果出错,可从 /var/log/apache2/error.log 中找答案。

补充:该模块在 Google Code 上的预编译文件有 Bug,不读取 Cookies,导致提示找不到 /etc/apache2/ga_auth/(null) 文件。这时一个比较搞笑的 wordaround 就是把你的用户认证信息文件重命名为 (null),然后就可以配合这个 Bug 继续工作了。此时用户名输什么都可以,密码则是要保持正确的。这倒也别有一番风味…… via @jimmy_xu_wrk

转载请注明本文地址:http://wzyboy.im/post/869.html