使用 Docker 搭建抓包实验环境
《透视 HTTP 协议》这个专栏正式完结已经一年多了,感谢你的支持与鼓励。
这一年的时间下来,我发现专栏"实验环境的搭建"确实是一个比较严重的问题:虽然我已经尽量把 Windows、macOS、Linux 里的搭建步骤写清楚了,但因为具体的系统环境千差万别,总会有各式各样奇怪的问题出现,比如端口冲突、目录权限等等。
所以,为了彻底解决这个麻烦,我特意制作了一个 Docker 镜像,里面是完整可用的 HTTP 实验环境,下面我就来详细说一下该怎么用。
安装 Docker 环境
因为我不知道你对 Docker 是否了解,所以第一步我还是先来简单介绍一下它。
Docker 是一种虚拟化技术,基于 Linux 的容器机制(Linux Containers,简称 LXC),你可以把它近似地理解成是一个"轻量级的虚拟机",只消耗较少的资源就能实现对进程的隔离保护。
使用 Docker 可以把应用程序和它相关的各种依赖(如底层库、组件等)"打包"在一起,这就是 Docker 镜像(Docker image)。Docker 镜像可以让应用程序不再顾虑环境的差异,在任意的系统中以容器的形式运行(当然必须要基于 Docker 环境),极大地增强了应用部署的灵活性和适应性。
Docker 是跨平台的,支持 Windows、macOS、Linux 等操作系统,在 Windows、macOS 上只要下载一个安装包,然后简单点几下鼠标就可以完成安装。

下面我以 Ubuntu 为例,说一下在 Linux 上的安装方法。
你可以在 Linux 上用 apt-get 或者 yum 安装 Docker,不过更好的方式是使用 Docker 官方提供的脚本,自动完成全套的安装步骤。
因为 Docker 是国外网站,直接从官网安装速度可能比较慢。所以你还可以选择国内的镜像网站来加快速度,像这里我就使用"–mirror"选项指定了"某某云"。
Docker 是 C/S 架构,安装之后还需要再执行一条命令启动它的服务。
sudo service docker start
此外,操作 Docker 必须要有 sudo 权限,你可以用"usermod"命令把当前的用户加入"Docker"组里。如果图省事,也可以用 sudo 命令直接切换成 root 用户来操作。
sudo usermod -aG docker ${USER}
sudo su -
这些做完后,你需要执行命令"docker version""docker info"来检查是否安装成功。比如下面这张图,显示的就是我使用的 Docker 环境,版本是"18.06.3-ce"。

获取 Docker 镜像
如果你已经安装好了 Docker 运行环境,现在就可以从 Docker Hub 上获取课程相应的 Docker 镜像文件了,用的是"docker pull"命令。
docker pull chronolaw/http_study
这个镜像里打包了操作系统 Ubuntu 18.04 和最新的 Openresty 1.17.8.2,还有项目的全部示例代码。为了方便你学习,我还在里面加入了 Vim、Git、Telnet、Curl、Tcpdump 等实用工具。
由于镜像里的东西多,所以体积比较大,下载需要一些时间,你要有点耐心。镜像下载完成之后,你可以用"Docker images"来查看结果,列出目前本地的所有镜像文件。

从图中你可以看到,这个镜像的名字是"chronolaw/http_study",大小是 645MB。
启动 Docker 容器
有了镜像文件,你就可以用"docker run"命令,从镜像启动一个容器了。
这里面就是我们完整的 HTTP 实验环境,不需要再操心这样、那样的问题了,做到了真正的"开箱即用"。
docker run -it --rm chronolaw/http_study
对于上面这条命令,我还要稍微解释一下:"-it"参数表示开启一个交互式的 Shell,默认使用的是 bash;"–rm"参数表示容器是"用完即扔",不保存容器实例,一旦退出 Shell 就会自动删除容器(但不会删除镜像),免去了管理容器的麻烦。
"docker run"之后,你就会像虚拟机一样进入容器的运行环境,这里就是 Ubuntu 18.04,身份也自动变成了 root 用户,大概是下面这样的。
docker run -it --rm chronolaw/http_study
root@8932f62c972:/
项目的源码我放在了 root 用户目录下,你可以直接进入"http_study/www"目录,然后执行"run.sh"启动 OpenResty 服务(可参考第 41 讲)。
cd ~/http_study/www
./run.sh start
不过因为 Docker 自身的限制,镜像里的 hosts 文件不能直接添加"www.chrono.com"等实验域名的解析。如果你想要在 URI 里使用域名,就必须在容器启动后手动修改 hosts 文件,用 Vim 或者 cat 都可以。
vim /etc/hosts
cat ~/http_study/hosts >> /etc/hosts
另一种方式是在"docker run"的时候用"–add-host"参数,手动指定域名 /IP 的映射关系。
docker run -it --rm --add-host=www.chrono.com:127.0.0.1 chronolaw/http_study
保险起见,我建议你还是用第一种方式比较好。也就是启动容器后,用"cat"命令,把实验域名的解析追加到 hosts 文件里,然后再启动 OpenResty 服务。
docker run -it --rm chronolaw/http_study
cat ~/http_study/hosts >> /etc/hosts
cd ~/http_study/www
./run.sh start
在 Docker 容器里做实验
把上面的工作都做完之后,我们的实验环境就算是完美地运行起来了,现在你就可以在里面任意验证各节课里的示例了,我来举几个例子。
不过在开始之前,我要提醒你一点,因为这个 Docker 镜像是基于 Linux 的,没有图形界面,所以只能用命令行(比如 telnet、curl)来访问 HTTP 服务。当然你也可以查一下资料,让容器对外暴露 80 等端口(比如使用参数"–net=host"),在外部用浏览器来访问,这里我就不细说了。
先来看最简单的,第 7 讲里的测试实验环境,用 curl 来访问 localhost,会输出一个文本形式的 HTML 文件内容。

然后我们来看第 9 讲,用 telnet 来访问 HTTP 服务,输入"telnet 127.0.0.1 80",回车,就进入了 telnet 界面。
Linux 下的 telnet 操作要比 Windows 的容易一些,你可以直接把 HTTP 请求报文复制粘贴进去,再按两下回车就行了,结束 telnet 可以用"Ctrl+C"。
GET /09-1 HTTP/1.1
Host: www.chrono.com

实验环境里测试 HTTPS 和 HTTP/2 也是毫无问题的,只要你按之前说的,正确修改了 hosts 域名解析,就可以用 curl 来访问,但要加上"-k"参数来忽略证书验证。


这里要注意一点,因为 Docker 镜像里的 Openresty 1.17.8.2 内置了 OpenSSL1.1.1g,默认使用的是 TLS1.3,所以如果你想要测试 TLS1.2 的话,需要使用参数"–tlsv1.2"。

在 Docker 容器里抓包
到这里,课程中的大部分示例都可以运行了。最后我再示范一下在 Docker 容器里 tcpdump 抓包的用法。
首先,你要指定抓取的协议、地址和端口号,再用"-w"指定存储位置,启动 tcpdump 后按"Ctrl+Z"让它在后台运行。比如为了测试 TLS1.3,就可以用下面的命令行,抓取 HTTPS 的 443 端口,存放到"/tmp"目录。
tcpdump tcp port 443 -i lo -w /tmp/a.pcap
然后,我们执行任意的 telnet 或者 curl 命令,完成 HTTP 请求之后,输入"fg"恢复 tcpdump,再按"Ctrl+C",这样抓包就结束了。
对于 HTTPS 需要导出密钥的情形,你必须在 curl 请求的同时指定环境变量"SSLKEYLOGFILE",不然抓包获取的数据无法解密,你就只能看到乱码了。
SSLKEYLOGFILE\=/tmp/a.log curl https:
我把完整的抓包过程截了个图,你可以参考一下。

抓包生成的文件在容器关闭后就会消失,所以还要用"docker cp"命令及时从容器里拷出来(指定容器的 ID,看提示符,或者用"docker ps -a"查看,也可以从 GitHub 仓库里获取 43-1.pcap/43-1.log)。
docker cp xxx:/tmp/a.pcap .
docker cp xxx:/tmp/a.log .
现在有了 pcap 文件和 log 文件,我们就可以用 Wireshark 来看网络数据,细致地分析 HTTP/HTTPS 通信过程了(HTTPS 还需要设置一下 Wireshark,见第 26 讲)。

在这个包里,你可以清楚地看到,通信时使用的是 TLS1.3 协议,服务器选择的密码套件是 TLS_AES_256_GCM_SHA384。
掌握了 tcpdump 的用法之后,你也可以再参考第 27 讲,改改 Nginx 配置文件,自己抓包仔细研究 TLS1.3 协议的"supported_versions""key_share""server_name"等各个扩展协议。