这不是个笑话。访问 connectionreset.net 你会立即得到一个连接重置错误。
由来
之前想写一个线上的网站连接诊断工具,能够检测网站为什么连不上,到底是因为主机关机了、服务没开启还是被阻止了。
于是,买了 connectionreset.net。
但在写网络诊断程序的时候,我发现一个最难生成的错误,就是连接重置(ERR_CONNECTION_RESET)。
我一直用的办法是触发某玩意的关键字 TCP 阻断,即用不加密的 HTTP 访问一个境外网站,在末尾传递关键词参数,比如:
curl http://supergiantgames.com/?.youtube.com
便会触发连接重置。但这样有一个麻烦的问题,就是一旦触发连接重置,这个状态会持续一分钟。一分钟之内就算删掉关键词再次访问,连接仍会被重置。
于是我想干脆做一个 “连接重置” 错误提供服务,让人们访问 connectionreset.net 能够立即得到一个连接重置错误。
各种尝试
要重置一个 TCP 连接,需要发送 RST 包。当主机收到 RST 包后会立即断开连接。
首先我尝试了 tcpkill 工具,不尽人意。tcpkill 监听 80 端口,通过抓包,新的连接建立后,tcpkill 作为中间人向两边发送 RST 来阻断 TCP 连接。装了 nginx,nginx 的默认实例网页包含文档和一个图片,但是访问的时候,文档正常地被载入,图片的载入被中断了。所以,效果不尽人意。。
其次尝试了 iptables,使用 tcp-reset 来拒绝 80 端口的连接:
iptables -A INPUT -i eth0 -p tcp --dport 80 -j REJECT --reject-with tcp-reset
但是通过浏览器访问,得到的结果是 Connection Refused,不是被重置,因此也不行。
解决方案
最后在 StackOverFlow 找到了这段 PHP 代码:https://stackoverflow.com/a/3773798
#!/usr/bin/php -q <?php set_time_limit(0); $sock = socket_create(AF_INET, SOCK_STREAM, 0); socket_bind($sock, '0.0.0.0', 80) or die('Could not bind to address'); socket_listen($sock); $client = socket_accept($sock); sleep(1); $pid = getmypid(); exec("kill -9 $pid"); ?>
实现方式是在 Socket 读取前强制关闭连接,这样会触发 TCP 连接的强制终止,即连接重置。
在接下来,需要做两个工作,一个是写一个 systemd 服务配置,让他能够在被杀掉后重新启动,另一个是建一个 cron job,让这个服务每分钟重启一次。
完成了。现在就可以很方便地生成一个连接重置错误了。