2023年2月

linux 上如何测试一个网页能不能用

今天有个同事说, 她在生产环境某个container里面的代码访问某个网页页面, 返回的结果不是期望的. 能不能一起看看到底是哪里出错了? 竟然有这么有趣的问题, 于是满口答应, 一块去探个究竟.

当然, 第一步就是去重现一下场景. 于是kubectl exec <pod_id> -n <ns> bash 登录container, 想着使用curl去模拟访问, 结果很沮丧. 为啥, 这个 container 真干净, 上面的代码是Java写的, 只有jdk, 没有curl, 没有wget, 让我怎么复现? 难道要自己写个Java Client, 打包编译, 然后去复现?

除了使用Java代码去实现访问网页http请求, 其他真没办法了吗? 让我们整理一下在Linux上常见的使用工具访问web网页的办法.

使用 curl

curl 是最方便的测试网页访问的工具, 很多Linux发布版都带curl, 支持很多协议.

$ curl -vvv 'https://www.tianxiaohui.com/index.php/about.html'

使用 wget

wget 支持 http, https, ftp, ftps这些协议去访问某个页面或资源, 也很方便使用, 很多Linux 发行版也自带.

$ wget 'https://www.tianxiaohui.com/index.php/about.html'

使用 netcat | ncat

netcat 是 GNU 一个网络实用工具, 不过根据官方网站在2004年发完0.7.1版本之后, 就没更新过了, 我发现我 Ubuntu 使用的是openBSD 版本的(https://packages.debian.org/sid/netcat-openbsd). 它最常用的是打开端口, 建立连接, 至于http协议, 那要自己去实现. 实现http协议还好, 如果实现证书认证https, 那可是有点难度.

所以, 你看我只能拿到一个400的错误. 不过要是访问非https的, 还是很可行的.

$ printf “GET /index.php/about.html HTTP/1.0\r\nHost: www.tianxiaohui.com\r\n\r\n” | nc www.tianxiaohui.com 443
F<html><head><title>400 Bad Request</title></head><body>
<h2>HTTPS is required</h2>
<p>This is an SSL protected page, please use the HTTPS scheme instead of the plain HTTP scheme to access this URL.<br />
<blockquote>Hint: The URL should starts with <b>https</b>://</blockquote> </p>
<hr />
Powered By LiteSpeed Web Server<br />
<a href='http://www.litespeedtech.com'><i>http://www.litespeedtech.com</i></a>
</body></html>

有人称它为网络debug的瑞士军刀, 没有继续开发, 是不是可惜了. 所以鼎鼎大名的nmap工具下面, 发扬了netcat的功能, 开了一ncat, 比netcat 更好用. 还支持 ssl. 那必须测试一把

$ printf "GET /index.php/about.html HTTP/1.0\r\nHost: www.tianxiaohui.com\r\n\r\n" | ncat -v --ssl www.tianxiaohui.com 443

Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: SSL connection to 154.213.16.200:443.
Ncat: SHA-1 fingerprint: 8461 369A 0DE4 7401 AE26 4259 B0C2 CC9F FE0B 66F5
HTTP/1.0 200 OK
Connection: close
content-type: text/html; charset=UTF-8
x-pingback: https://www.tianxiaohui.com/index.php/action/xmlrpc
date: Thu, 16 Feb 2023 15:08:37 GMT
server: LiteSpeed
vary: User-Agent,User-Agent

<!DOCTYPE HTML>
<html class="no-js">
<head>
...省略
</html>

完美拿到数据

使用 openssl

openssl 是一个开源的, 通用的加密安全的工具软件, 很多Linux 发行版都带了它, 使用它进行网页访问, 也不在话下.

$printf "GET /index.php/about.html HTTP/1.0\r\nHost: www.tianxiaohui.com\r\n\r\n" | openssl s_client -quiet -state -connect www.tianxiaohui.com:443
SSL_connect:before SSL initialization
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS read server hello
SSL_connect:TLSv1.3 read encrypted extensions
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = www.tianxiaohui.com
verify return:1
SSL_connect:SSLv3/TLS read server certificate
SSL_connect:TLSv1.3 read server certificate verify
SSL_connect:SSLv3/TLS read finished
SSL_connect:SSLv3/TLS write change cipher spec
SSL_connect:SSLv3/TLS write finished
HTTP/1.0 200 OK

仅仅使用 bash, 你没看错

如果你的container里面非常干净, 上面的软件全没有, 但恰巧你安装的是bash, 那么这个任务还是能完成. 仔细看:

supra@suprabox:~$ exec 4<>/dev/tcp/www.tianxiaohui.com/443
supra@suprabox:~$ echo -e "GET /index.php/about.html HTTP/1.0\r\nHost: www.tianxiaohui.com\r\n\r\n" >&4
supra@suprabox:~$ cat  <&4
<html><head><title>400 Bad Request</title></head><body>
<h2>HTTPS is required</h2>
<p>This is an SSL protected page, please use the HTTPS scheme instead of the plain HTTP scheme to access this URL.<br />
<blockquote>Hint: The URL should starts with <b>https</b>://</blockquote> </p>
<hr />
Powered By LiteSpeed Web Server<br />
<a href='http://www.litespeedtech.com'><i>http://www.litespeedtech.com</i></a>
</body></html>

虽然, 能拿到结果, 但是没有ssl协商, 所以网站只给了一个400. 所以 bash 这种方式, 对于https只能验证连接成功, 不能验证拿到的结果.

那么我们就拿一个http的网站来练习一下, 可是如今到哪里去找一个还支持http的网站呢, 哈哈, 还真找到一个. 如下:

supra@suprabox:~$ exec 5<>/dev/tcp/www.henu.edu.cn/80
supra@suprabox:~$ echo -e "GET /index.htm HTTP/1.0\r\nHost: www.henu.edu.cn\r\n\r\n" >&5
supra@suprabox:~$ less  <&5

完美拿到结果, 自己处理ssl 太难了, 不过对于仅仅http, 还是很管用的.

对于上面3步的解释:

  1. /dev/protocol/host/port 是bash的一个重定向文件, 它帮你建立一个连接, 协议可以是tcp或udp, host可以是主机名或ip, 所以第一行通过exec建立一个文件描述符是5的重定向.
  2. 第二步是把 http 协议的request 发送到这个连接
  3. 第三步读出返回的结果网页.

总结

上面的很多工具都很好用, 有些还支持设置代理, 设置timeout 等, 据有相当丰富的功能. 在container 里面受限的情况下, 还有哪些好用的网页测试工具? 当然, 你说我container 里面有 python, 有 perl, wow, 那你太幸福了.

查看一个socket的开始时间

问题起源

有一个服务分布在3个数据中心, 有一个统一的FQDN(svc1.vip.tianxioahui.com)让别人去访问它, 同时每个数据中心都有一个单独的IP来访问该服务, 也就是说这个域名能解析到3个不同的IP. 有一次某个数据中心的该服务出现了点问题, 于是对应的IP被从DNS服务器上临时去掉了. 当问题修复之后, DNS服务器上恢复对应的IP, 该数据中心的流量却没有恢复, 还是流向了其它2个数据中心. 问题到底出现在哪呢?

初步调查

该服务的使用方是我们内部的一个服务, 于是我们首先检查客户端的DNS解析. 同时, 我们的DNS服务器会根据查询者所在的数据中心首先返回本数据中心的IP, 当没有对应数据中心的IP之后, 才会返回不同的数据中心的IP.

正常情况下的服务访问情况:
Screen Shot 2023-02-10 at 20.47.20.png

当 DC-C 的服务出问题的时候:
Screen Shot 2023-02-10 at 20.49.26.png

于是我们登录之前出问题数据中心某个客户端, 查询 DNS 解析情况, 发现查询的结果就是已经恢复的数据中心的IP, 也就是DNS解析已经完全恢复.

于是通过 ss 去查看现存的tcp连接, 发现确实是连接到其它数据中心的.

为什么恢复后还连接到其它数据中心?

首先经过连续多次测试, DNS解析的结果仍然是当前数据中心的IP, 所以DNS解析完全是正常的. 所以怀疑这个连接是之前早就建立好的, 并且一直 keep-alive, 所以一直还是正常在用的.

如何确定这个连接已经存活多久了?

我们想通过最简单的方式: ss 命令, 看看它是否提供了一个tcp连接的开始时间man ss答案是, 并没有.

于是, 我们找到这篇: https://superuser.com/questions/565991/how-to-determine-the-socket-connection-up-time-on-linux

这个问题的最高评答案里面给的过程是:

  1. 找到这个tcp连接所在的进程和proc文件系统的inode
  2. 然后使用stat命令去查看文件的最后访问时间(回答里用的是: query the file access time of the symbolic link)

然而这个并不是socket uptime, 而是socket最后的访问时间, 可是那个shell函数又命名为suptime, 真是对不上.

但是, 他使用stat访问文件的创建时间是对的

文件创建时间

通常情况下,使用stat都能查看文件的创建时间, 比如:

supra@suprabox:~$ stat ./bootstrap.sh
  File: ./bootstrap.sh
  Size: 4162          Blocks: 16         IO Block: 4096   regular file
Device: fd01h/64769d    Inode: 10763859    Links: 1
Access: (0775/-rwxrwxr-x)  Uid: ( 1000/   supra)   Gid: ( 1000/   supra)
Access: 2022-11-22 19:06:03.507882931 -0800
Modify: 2022-11-22 19:05:59.487695031 -0800
Change: 2022-11-22 19:05:59.491695212 -0800
 Birth: 2022-11-22 19:05:59.487695031 -0800

上面最后一行 Birth 就是它的创建时间.

查看socket inode的创建时间

其实上面输出中的 Birth 是在 inode 的元数据里的, 所以, 如果我们按照那个答案的方法去访问socket虚拟文件的inode是不是就找到了socket 的创建时间?

sudo ss -t -p -e
State    Recv-Q  Send-Q   Local Address:Port     Peer Address:Port      Process
ESTAB    0       0        10.249.64.103:ssh      10.226.199.52:61340    users:(("sshd",pid=812231,fd=4),("sshd",pid=812101,fd=4)) timer:(keepalive,54min,0) ino:2211652 sk:5063 cgroup:/system.slice/ssh.service <->

ss命令通过制定 -p 参数, 现实进程号和fd信息(pid=812101,fd=4), 通过-e参数, 显示inode 信息(ino:2211652). 于是, 我们可以查看这个虚拟文件的inode 信息了

supra@suprabox:~$ sudo stat /proc/812101/fd/4
  File: /proc/812101/fd/4 -> socket:[2211652]
  Size: 64            Blocks: 0          IO Block: 1024   symbolic link
Device: 17h/23d    Inode: 2228948     Links: 1
Access: (0700/lrwx------)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-02-11 01:51:35.510079439 -0800
Modify: 2023-02-11 01:51:26.442196746 -0800
Change: 2023-02-11 01:51:26.442196746 -0800
 Birth: -

可是Birth 这行是空.
文件的符号链接指向 socket:[2211652], 这个方括号里面的值就是我们通过ss查看得到的 inode id.

同时, 我们去查看 cat /proc/net/tcp 也能看到一样的行

最终, 也没找到一个可以查看socket创建时间的命令.