diff --git a/README.md b/README.md index 8f57253..ee092b9 100644 --- a/README.md +++ b/README.md @@ -58,12 +58,12 @@ mkdir CloudflareST cd CloudflareST # 下载 CloudflareST 压缩包(自行根据需求替换 URL 中 [版本号] 和 [文件名]) -wget -N https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.2.5/CloudflareST_linux_amd64.tar.gz +wget -N https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.0/CloudflareST_linux_amd64.tar.gz # 如果你是在国内网络环境中下载,那么请使用下面这几个镜像加速之一: -# wget -N https://ghp.ci/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.2.5/CloudflareST_linux_amd64.tar.gz -# wget -N https://ghproxy.cc/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.2.5/CloudflareST_linux_amd64.tar.gz -# wget -N https://ghproxy.net/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.2.5/CloudflareST_linux_amd64.tar.gz -# wget -N https://gh-proxy.com/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.2.5/CloudflareST_linux_amd64.tar.gz +# wget -N https://ghp.ci/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.0/CloudflareST_linux_amd64.tar.gz +# wget -N https://ghproxy.cc/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.0/CloudflareST_linux_amd64.tar.gz +# wget -N https://ghproxy.net/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.0/CloudflareST_linux_amd64.tar.gz +# wget -N https://gh-proxy.com/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.0/CloudflareST_linux_amd64.tar.gz # 如果下载失败的话,尝试删除 -N 参数(如果是为了更新,则记得提前删除旧压缩包 rm CloudflareST_linux_amd64.tar.gz ) # 解压(不需要删除旧文件,会直接覆盖,自行根据需求替换 文件名) @@ -93,20 +93,20 @@ chmod +x CloudflareST ### 结果示例 -测速完毕后,默认会显示**最快的 10 个 IP**,示例: +测速完毕后,默认会显示**最快的 10 个 IP**,示例(仅为输出内容示例): ``` bash -IP 地址 已发送 已接收 丢包率 平均延迟 下载速度 (MB/s) -104.27.200.69 4 4 0.00 146.23 28.64 -172.67.60.78 4 4 0.00 139.82 15.02 -104.25.140.153 4 4 0.00 146.49 14.90 -104.27.192.65 4 4 0.00 140.28 14.07 -172.67.62.214 4 4 0.00 139.29 12.71 -104.27.207.5 4 4 0.00 145.92 11.95 -172.67.54.193 4 4 0.00 146.71 11.55 -104.22.66.8 4 4 0.00 147.42 11.11 -104.27.197.63 4 4 0.00 131.29 10.26 -172.67.58.91 4 4 0.00 140.19 9.14 +IP 地址 已发送 已接收 丢包率 平均延迟 下载速度(MB/s) 地区码 +104.27.200.69 4 4 0.00 146.23 28.64 LAX +172.67.60.78 4 4 0.00 139.82 15.02 SEA +104.25.140.153 4 4 0.00 146.49 14.90 SJC +104.27.192.65 4 4 0.00 140.28 14.07 LAX +172.67.62.214 4 4 0.00 139.29 12.71 LAX +104.27.207.5 4 4 0.00 145.92 11.95 LAX +172.67.54.193 4 4 0.00 146.71 11.55 LAX +104.22.66.8 4 4 0.00 147.42 11.11 SEA +104.27.197.63 4 4 0.00 131.29 10.26 FRA +172.67.58.91 4 4 0.00 140.19 9.14 SJC ... # 如果平均延迟非常低(如 0.xx),则说明 CloudflareST 测速时走了代理,请先关闭代理软件后再测速。 @@ -132,8 +132,8 @@ IP 地址 已发送 已接收 丢包率 平均延迟 下载速度 完整结果保存在当前目录下的 `result.csv` 文件中,用**记事本/表格软件**打开,格式如下: ``` -IP 地址,已发送,已接收,丢包率,平均延迟,下载速度 (MB/s) -104.27.200.69,4,4,0.00,146.23,28.64 +IP 地址,已发送,已接收,丢包率,平均延迟,下载速度(MB/s),地区码 +104.27.200.69,4,4,0.00,146.23,28.64,LAX ``` > _大家可以按自己需求,对完整结果**进一步筛选处理**,或者去看一看进阶使用**指定过滤条件**!_ @@ -163,15 +163,17 @@ https://github.com/XIU2/CloudflareSpeedTest 指定测速端口;延迟测速/下载测速时使用的端口;(默认 443 端口) -url https://cf.xiu2.xyz/url 指定测速地址;延迟测速(HTTPing)/下载测速时使用的地址,默认地址不保证可用性,建议自建; + 当下载测速时,软件会从 HTTP 响应头中获取该 IP 当前的机场地区码(支持 Cloudflare、AWS CloudFront)并显示出来。 -httping 切换测速模式;延迟测速模式改为 HTTP 协议,所用测试地址为 [-url] 参数;(默认 TCPing) + 当使用 HTTP 测速模式时,软件会从 HTTP 响应头中获取该 IP 当前的机场地区码(支持 Cloudflare、AWS CloudFront)并显示出来。 注意:HTTPing 本质上也算一种 网络扫描 行为,因此如果你在服务器上面运行,需要降低并发(-n),否则可能会被一些严格的商家暂停服务。 如果你遇到 HTTPing 首次测速可用 IP 数量正常,后续测速越来越少甚至直接为 0,但停一段时间后又恢复了的情况,那么也可能是被 运营商、Cloudflare CDN 认为你在网络扫描而 触发临时限制机制,因此才会过一会儿就恢复了,建议降低并发(-n)减少这种情况的发生。 -httping-code 200 有效状态代码;HTTPing 延迟测速时网页返回的有效 HTTP 状态码,仅限一个;(默认 200 301 302) -cfcolo HKG,KHH,NRT,LAX,SEA,SJC,FRA,MAD - 匹配指定地区;地区名为当地机场三字码,英文逗号分隔,支持小写,支持 Cloudflare、AWS CloudFront,仅 HTTPing 模式可用;(默认 所有地区) + 匹配指定地区;地区名为当地机场地区码,英文逗号分隔,支持小写,支持 Cloudflare、AWS CloudFront,仅 HTTPing 模式可用;(默认 所有地区) -tl 200 平均延迟上限;只输出低于指定平均延迟的 IP,各上下限条件可搭配使用;(默认 9999 ms) @@ -196,6 +198,9 @@ https://github.com/XIU2/CloudflareSpeedTest -allip 测速全部的IP;对 IP 段中的每个 IP (仅支持 IPv4) 进行测速;(默认 每个 /24 段随机测速一个 IP) + -debug + 调试输出模式;会在一些非预期情况下输出更多日志以便判断原因;(默认 关闭) + -v 打印程序版本 + 检查版本更新 -h @@ -220,10 +225,10 @@ https://github.com/XIU2/CloudflareSpeedTest 321 / 321 [-----------------------------------------------------------] 可用: 30 开始下载测速(下限:1.00 MB/s, 数量:5, 队列:10) 3 / 5 [-----------------------------------------↗--------------------] -IP 地址 已发送 已接收 丢包率 平均延迟 下载速度 (MB/s) -XXX.XXX.XXX.XXX 4 4 0.00 83.32 3.66 -XXX.XXX.XXX.XXX 4 4 0.00 107.81 2.49 -XXX.XXX.XXX.XXX 4 3 0.25 149.59 1.04 +IP 地址 已发送 已接收 丢包率 平均延迟 下载速度(MB/s) 地区码 +XXX.XXX.XXX.XXX 4 4 0.00 83.32 3.66 LAX +XXX.XXX.XXX.XXX 4 4 0.00 107.81 2.49 LAX +XXX.XXX.XXX.XXX 4 3 0.25 149.59 1.04 N/A 完整测速结果已写入 result.csv 文件,可使用记事本/表格软件查看。 按下 回车键 或 Ctrl+C 退出。 @@ -266,7 +271,7 @@ CloudflareST 会先延迟测速,在这过程中进度条右侧会实时显示 **** -另外,如果全部队列 IP 都测速完了,但一个满足下载速度条件的 IP 都没有,那么就会**直接输出全部队列 IP 的下载测速结果**,这样你就能看到这些 IP 的下载速度都有多少,心里也就有数了,然后**适当调低 `-sl` 再试试**。 +另外,如果全部队列 IP 都测速完了,但一个满足下载速度条件的 IP 都没有,你可能需要调低预期的下载测速下限条件,但你需要知道当前的大概测速速度都在什么范围,那么你就可以加上 `-debug` 参数开启调试模式,这样再遇到这种情况时,就会**忽略条件返回所有测速数据**,你就能看到这些 IP 的下载速度都有多少,心里也就有数了,然后**适当调低 `-sl` 再试试**。 同样,延迟测速方面,`可用: 30`、`队列:10` 这两个数值也可以让你清楚,你设置的延迟条件对你来说是否过于苛刻。如果可用 IP 一大堆,但条件过滤后只剩下 2、3 个,那不用说就知道需要**调低预期的延迟/丢包条件**了。 @@ -383,6 +388,8 @@ HTTP 协议适用于快速测试某域名指向某 IP 时是否可以访问, > 另外,本软件 HTTPing 仅获取**响应头(response headers)**,并不获取正文内容(即 URL 文件大小不影响 HTTPing 测试,但如果你还要下载测速的话,那么还是需要一个大文件的),类似于 curl -i 功能。 +> 另外,HTTPing 过程中,软件会从 HTTP 响应头中获取该 IP 当前的机场地区码(支持 Cloudflare、AWS CloudFront)并显示出来,而 TCPing 过程中无法这样做(但 下载测速 时也会这样做来获取地区码,毕竟下载测速也是个 HTTP 链接) + ``` bash # 只需加上 -httping 参数即可切换到 HTTP 协议延迟测速模式 CloudflareST.exe -httping @@ -403,7 +410,7 @@ CloudflareST.exe -httping -tp 80 -url http://cdn.cloudflare.steamstatic.com/stea **** -#### \# 匹配指定地区(colo 机场三字码) +#### \# 匹配指定地区(colo 机场地区码)
「 点击展开 查看内容 」 @@ -420,22 +427,24 @@ Cloudflare CDN 的节点 IP 是 Anycast IP,即每个 IP 对应的服务器节 因此,对于这种 Anycast IP 的实际服务器位置,就不能靠那些在线 IP 地址位置查询网站来判断了。 -除了通过 **HTTP 响应头**获取机场三字码外(该功能的实现方式),还可以手动访问 `http://CloudflareIP/cdn-cgi/trace` 来获知 CDN 分配给你的实际节点地区机场三字码。 +除了通过 **HTTP 响应头**获取机场地区码外(该功能的实现方式),还可以手动访问 `http://CloudflareIP/cdn-cgi/trace` 来获知 CDN 分配给你的实际节点地区机场地区码。 -> 该功能支持 Cloudflare CDN 和 AWS CloudFront CDN,且这两个 CDN 的机场三字码是通用的(算是惯例)。 +> 该功能支持 Cloudflare CDN 和 AWS CloudFront CDN,且这两个 CDN 的机场地区码是通用的(算是惯例)。 > **注意**:如果你要用于筛选 AWS CloudFront CDN 地区,那么要通过 `-url` 参数指定一个使用 AWS CloudFront CDN 的下载测速地址(因为软件默认下载测速地址是 Cloudflare CDN 的) ``` bash # 指定地区名后,延迟测速后得到的结果就都是指定地区的 IP 了(如果没有指定 -dd 的话则会继续进行下载测速) # 如果延迟测速后结果为 0,则说明没有找到任何一个(未超时可用的)指定地区的 IP。 -# 节点地区名为当地 机场三字码,指定多个时用英文逗号分隔,v2.2.3 版本后支持小写 +# 节点地区名为当地 机场地区码,指定多个时用英文逗号分隔,v2.2.3 版本后支持小写 CloudflareST.exe -httping -cfcolo HKG,KHH,NRT,LAX,SEA,SJC,FRA,MAD -# 注意,该参数只有在 HTTPing 延迟测速模式下才可用(因为软件是通过 HTTP 链接中的响应头来获得该 IP 的实际地区机场三字码) +# 注意,该参数只有在 HTTPing 延迟测速模式下才可用(因为软件是通过 HTTP 链接中的响应头来获得该 IP 的实际地区机场地区码) + +# 另外,HTTPing 过程中,软件会从 HTTP 响应头中获取该 IP 当前的机场地区码(支持 Cloudflare、AWS CloudFront)并显示出来,而 TCPing 过程中无法这样做(但 下载测速 时也会这样做来获取地区码,毕竟下载测速也是个 HTTP 链接) ``` -> 两个 CDN 机场三字码通用,因此各地区名可见:https://www.cloudflarestatus.com/ +> 两个 CDN 机场地区码通用,因此各地区名可见:https://www.cloudflarestatus.com/
@@ -574,7 +583,7 @@ CloudflareST.exe -tlr 0.25 CloudflareST.exe -sl 5 -dn 10 ``` -> 如果**没有找到一个满足速度**条件的 IP,那么会**忽略条件输出所有 IP 测速结果**(方便你下次测速时调整条件)。 +> 如果**没有找到一个满足速度**条件的 IP,那么不会输出任何内容,你可能需要调低预期的下载测速下限条件,但你需要知道当前的大概测速速度都在什么范围,那么你就可以加上 `-debug` 参数开启调试模式,这样再遇到这种情况时,就会**忽略条件返回所有测速数据**,你就能看到这些 IP 的下载速度都有多少,心里也就有数了,然后**适当调低 `-sl` 再试试**。 > 没有指定平均延迟上限时,如果一直**凑不够**满足条件的 IP 数量,就会**一直测速**下去。 > 所以建议**同时指定 [下载速度下限] + [平均延迟上限]**,这样测速到指定延迟上限还没凑够数量,就会终止测速。 @@ -591,7 +600,7 @@ CloudflareST.exe -tl 200 -sl 5.6 -dn 10 ``` > 如果**没有找到一个满足延迟**条件的 IP,那么不会输出任何内容。 -> 如果**没有找到一个满足速度**条件的 IP,那么会忽略条件输出所有 IP 测速结果(方便你下次测速时调整条件)。 +> 如果**没有找到一个满足速度**条件的 IP,那么不会输出任何内容,但可以通过加上 `-debug` 参数开启调试模式,这时会忽略条件输出所有 IP 测速结果(方便你下次测速时调整条件)。 > 所以建议先不指定条件测速一遍,看看平均延迟和下载速度大概在什么范围,避免指定条件**过低/过高**! > 因为 Cloudflare 公开的 IP 段是**回源 IP+任播 IP**,而**回源 IP**是无法使用的,所以下载测速是 0.00。 @@ -652,6 +661,64 @@ CloudflareST.exe -f 1.txt **** +#### \# 下载测速都是 0.00 ? + +
+「 点击展开 查看内容 」 + +**** + +首先要明白,本软件的下载测速过程,本质上和你将 `IP 下载测速地址的域名` 写入 hosts 文件,然后浏览器去访问下载测速地址是一样的,只不过软件将其自动化了(类似于 `curl -I --resolve 下载测速地址的域名:443:IP https://下载测速地址`)。 + +因此如果下载测速结果全都是 0.00 MB/s,那么以为着下载测速失败,就只有这几种可能性。 + +1. **下载测速地址有问题** +2. **测速的 IP 地址有问题** +3. **你的网络有问题** + +但在排查具体是哪个问题前,可以先在你原先的 CloudflareST 运行命令后追加一个 `-debug` 参数来开启调试模式,重新跑一边测速,这样下载过程中报错了就能直接看到下载测速失败的具体原因。 + +常见的下载测速失败报错原因有(因为是原生报错信息,因此基本都是英文): + +1. `... read: connection reset by peer ... ` +下载测速地址被阻断了,可能是蔷干的,也可能是运营商干的(比如移动或部分地区的白名单) +2. `... HTTP 状态码: 403 ...` +像这种直接提示 HTTP 状态码的,比较好判断,如 403 就是测速地址禁止你访问,404 就是测速地址找不到文件,具体可以搜索 HTTP 状态码含义 +3. `... context deadline exceeded (Client.Timeout exceeded while awaiting headers) ...` +这种一般是超时引起的,可能是 IP 等网络问题,也可能是 -dt 下载测速时间设置的太短了,当然默认的 10 秒到不至于超时 + +> 如果你遇到了其他报错原因,且翻译后还是不懂,可以发 Issues 或 Discussions 询问。 + +根据上面的报错原因排查一遍后,如果还是无法解决,那么可以尝试下面这些: + +**一、下载测速地址有问题**: + +先去 [#490](https://github.com/XIU2/CloudflareSpeedTest/discussions/490) 找几个其他的下载测速地址都试试。 + +如果其中有能下载测速出结果的,则就代表你之前使用的下载测速地址有问题(注意,目前默认下载测速地址仅为一个带负载均衡轮询的重定向链接,会自动重定向到上面帖子里大家分享的公益下载测速地址,而这些地址在**不同地区的可用性可能有差异**,因此可能出现之前不行现在又正常的情况,如果**想要稳定,建议自建**,上面帖子写了几种自建方法)。 + +如果找了很多,都是一样 0.00,那么就要考虑其他可能性了。 + +**** + +**二、测速的 IP 地址有问题**: + +你用来测速的 IP 地址,可能一些 TCP 测试是通的,但实际上因为各种原因导致不能建立 HTTP 链接(比如是回源 IP,比如是企业用户专用 IP 等等),因此你可以多尝试一些其他的 IP 看是否可行。 + +**** + +**三、你的网络有问题**: + +这个就比较麻烦了,如果你现在是用电脑+宽带来使用 CloudflareST 测速的,那么可以尝试关闭手机 WIFI 并打开流量,然后数据线连接电脑,设置好 USB 网络共享(不同手机系统不太一样,具体自行搜索哈),并拔掉电脑的网线,这样你的电脑现在就是走的手机流量数据网络了(如果手机流量数据和宽带不是一个运营商会更好排查),然后再次运行 CloudflareST 测速看看结果是否改变(也可以同时尝试上面的排查方法来交叉验证)。 + +如果测速结果正常了,那么显然就是宽带网络的问题,如果还是一样的 0.00,那么就麻烦了。。。 + +**** + +
+ +**** + #### \# 一劳永逸加速所有使用 Cloudflare CDN 的网站(不需要再一个个添加域名到 Hosts 了) 我以前说过,开发该软件项目的目的就是为了通过**改 Hosts 的方式来加速访问使用 Cloudflare CDN 的网站**。 diff --git a/main.go b/main.go index 9ae5b26..d6cf8e1 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ https://github.com/XIU2/CloudflareSpeedTest -httping-code 200 有效状态代码;HTTPing 延迟测速时网页返回的有效 HTTP 状态码,仅限一个;(默认 200 301 302) -cfcolo HKG,KHH,NRT,LAX,SEA,SJC,FRA,MAD - 匹配指定地区;地区名为当地机场三字码,英文逗号分隔,仅 HTTPing 模式可用;(默认 所有地区) + 匹配指定地区;地区名为当地机场地区码,英文逗号分隔,仅 HTTPing 模式可用;(默认 所有地区) -tl 200 平均延迟上限;只输出低于指定平均延迟的 IP,各上下限条件可搭配使用;(默认 9999 ms) @@ -68,6 +68,9 @@ https://github.com/XIU2/CloudflareSpeedTest -allip 测速全部的IP;对 IP 段中的每个 IP (仅支持 IPv4) 进行测速;(默认 每个 /24 段随机测速一个 IP) + -debug + 调试输出模式;会在一些非预期情况下输出更多日志以便判断原因;(默认 关闭) + -v 打印程序版本 + 检查版本更新 -h @@ -99,12 +102,14 @@ https://github.com/XIU2/CloudflareSpeedTest flag.BoolVar(&task.Disable, "dd", false, "禁用下载测速") flag.BoolVar(&task.TestAll, "allip", false, "测速全部 IP") + flag.BoolVar(&utils.Debug, "debug", false, "调试输出模式") + flag.BoolVar(&printVersion, "v", false, "打印程序版本") flag.Usage = func() { fmt.Print(help) } flag.Parse() if task.MinSpeed > 0 && time.Duration(maxDelay)*time.Millisecond == utils.InputMaxDelay { - fmt.Println("[小提示] 在使用 [-sl] 参数时,建议搭配 [-tl] 参数,以避免因凑不够 [-dn] 数量而一直测速...") + fmt.Println("\033[33m[提示] 在使用 [-sl] 参数时,建议搭配 [-tl] 参数,以避免因凑不够 [-dn] 数量而一直测速...\033[0m") } utils.InputMaxDelay = time.Duration(maxDelay) * time.Millisecond utils.InputMinDelay = time.Duration(minDelay) * time.Millisecond diff --git a/task/download.go b/task/download.go index 26eac41..cd44cb6 100644 --- a/task/download.go +++ b/task/download.go @@ -54,7 +54,7 @@ func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSe return utils.DownloadSpeedSet(ipSet) } if len(ipSet) <= 0 { // IP数组长度(IP数量) 大于 0 时才会继续下载测速 - fmt.Println("\n[信息] 延迟测速结果 IP 数量为 0,跳过下载测速。") + fmt.Println("\n\033[33m[信息] 延迟测速结果 IP 数量为 0,跳过下载测速。\033[0m") return } testNum := TestCount @@ -65,7 +65,7 @@ func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSe TestCount = testNum } - fmt.Printf("开始下载测速(下限:%.2f MB/s, 数量:%d, 队列:%d)\n", MinSpeed, TestCount, testNum) + fmt.Printf("\033[34m开始下载测速(下限:%.2f MB/s, 数量:%d, 队列:%d)\033[0m\n", MinSpeed, TestCount, testNum) // 控制 下载测速进度条 与 延迟测速进度条 长度一致(强迫症) bar_a := len(strconv.Itoa(len(ipSet))) bar_b := " " @@ -74,8 +74,11 @@ func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSe } bar := utils.NewBar(TestCount, bar_b, "") for i := 0; i < testNum; i++ { - speed := downloadHandler(ipSet[i].IP) + speed, colo := downloadHandler(ipSet[i].IP) ipSet[i].DownloadSpeed = speed + if ipSet[i].Colo == "" { // 只有当 Colo 是空的时候,才写入,否则代表之前是 httping 测速并获取过了 + ipSet[i].Colo = colo + } // 在每个 IP 下载测速后,以 [下载速度下限] 条件过滤结果 if speed >= MinSpeed*1024*1024 { bar.Grow(1, "") @@ -86,7 +89,8 @@ func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSe } } bar.Done() - if len(speedSet) == 0 { // 没有符合速度限制的数据,返回所有测试数据 + if utils.Debug && len(speedSet) == 0 { // 调试模式下,没有满足速度限制的数据,返回所有测速数据供用户查看当前的测速结果,以便适当调低预期测速条件 + fmt.Println("\033[33m[调试] 没有满足 下载速度下限 条件的 IP,忽略条件返回所有测速数据(方便下次测速时调整条件)。\033[0m") speedSet = utils.DownloadSpeedSet(ipSet) } // 按速度排序 @@ -107,12 +111,15 @@ func getDialContext(ip *net.IPAddr) func(ctx context.Context, network, address s } // return download Speed -func downloadHandler(ip *net.IPAddr) float64 { +func downloadHandler(ip *net.IPAddr) (float64, string) { client := &http.Client{ Transport: &http.Transport{DialContext: getDialContext(ip)}, Timeout: Timeout, CheckRedirect: func(req *http.Request, via []*http.Request) error { if len(via) > 10 { // 限制最多重定向 10 次 + if utils.Debug { // 调试模式下,输出更多信息 + fmt.Printf("\033[31m[调试] IP: %s, 下载测速地址重定向次数过多,终止测速,URL: %s\033[0m\n", ip.String(), req.URL.String()) + } return http.ErrUseLastResponse } if req.Header.Get("Referer") == defaultURL { // 当使用默认下载测速地址时,重定向不携带 Referer @@ -123,19 +130,31 @@ func downloadHandler(ip *net.IPAddr) float64 { } req, err := http.NewRequest("GET", URL, nil) if err != nil { - return 0.0 + if utils.Debug { // 调试模式下,输出更多信息 + fmt.Printf("\033[31m[调试] IP: %s, 下载测速请求创建失败,错误信息: %v, URL: %s\033[0m\n", ip.String(), err, URL) + } + return 0.0, "" } req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36") response, err := client.Do(req) if err != nil { - return 0.0 + if utils.Debug { // 调试模式下,输出更多信息 + fmt.Printf("\033[31m[调试] IP: %s, 下载测速失败,错误信息: %v, URL: , 最终URL: %s%s\033[0m\n", ip.String(), err, URL, response.Request.URL.String()) + } + return 0.0, "" } defer response.Body.Close() if response.StatusCode != 200 { - return 0.0 + if utils.Debug { // 调试模式下,输出更多信息 + fmt.Printf("\033[31m[调试] IP: %s, 下载测速终止,HTTP 状态码: %d, URL: %s, 最终URL: %s\033[0m\n", ip.String(), response.StatusCode, URL, response.Request.URL.String()) + } + return 0.0, "" } + // 通过头部 Server 值判断是 Cloudflare 还是 AWS CloudFront 并设置 cfRay 为各自的机场地区码完整内容 + colo := getHeaderColo(response.Header) + timeStart := time.Now() // 开始时间(当前) timeEnd := timeStart.Add(Timeout) // 加上下载测速时间得到的结束时间 @@ -179,5 +198,5 @@ func downloadHandler(ip *net.IPAddr) float64 { } contentRead += int64(bufferRead) } - return e.Value() / (Timeout.Seconds() / 120) + return e.Value() / (Timeout.Seconds() / 120), colo } diff --git a/task/httping.go b/task/httping.go index 783a883..79d2d34 100644 --- a/task/httping.go +++ b/task/httping.go @@ -18,11 +18,11 @@ var ( HttpingStatusCode int HttpingCFColo string HttpingCFColomap *sync.Map - OutRegexp = regexp.MustCompile(`[A-Z]{3}`) + ColoRegexp = regexp.MustCompile(`[A-Z]{3}`) ) // pingReceived pingTotalTime -func (p *Ping) httping(ip *net.IPAddr) (int, time.Duration) { +func (p *Ping) httping(ip *net.IPAddr) (int, time.Duration, string) { hc := http.Client{ Timeout: time.Second * 2, Transport: &http.Transport{ @@ -35,84 +35,79 @@ func (p *Ping) httping(ip *net.IPAddr) (int, time.Duration) { } // 先访问一次获得 HTTP 状态码 及 Cloudflare Colo + var colo string { - requ, err := http.NewRequest(http.MethodHead, URL, nil) + request, err := http.NewRequest(http.MethodHead, URL, nil) if err != nil { - return 0, 0 + return 0, 0, "" } - requ.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36") - resp, err := hc.Do(requ) + request.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36") + response, err := hc.Do(request) if err != nil { - return 0, 0 + return 0, 0, "" } - defer resp.Body.Close() + defer response.Body.Close() - //fmt.Println("IP:", ip, "StatusCode:", resp.StatusCode, resp.Request.URL) + //fmt.Println("IP:", ip, "StatusCode:", response.StatusCode, response.Request.URL) // 如果未指定的 HTTP 状态码,或指定的状态码不合规,则默认只认为 200、301、302 才算 HTTPing 通过 if HttpingStatusCode == 0 || HttpingStatusCode < 100 && HttpingStatusCode > 599 { - if resp.StatusCode != 200 && resp.StatusCode != 301 && resp.StatusCode != 302 { - return 0, 0 + if response.StatusCode != 200 && response.StatusCode != 301 && response.StatusCode != 302 { + return 0, 0, "" } } else { - if resp.StatusCode != HttpingStatusCode { - return 0, 0 + if response.StatusCode != HttpingStatusCode { + return 0, 0, "" } } - io.Copy(io.Discard, resp.Body) + io.Copy(io.Discard, response.Body) - // 只有指定了地区才匹配机场三字码 + // 通过头部 Server 值判断是 Cloudflare 还是 AWS CloudFront 并设置 cfRay 为各自的机场地区码完整内容 + colo = getHeaderColo(response.Header) + + // 只有指定了地区才匹配机场地区码 if HttpingCFColo != "" { - // 通过头部 Server 值判断是 Cloudflare 还是 AWS CloudFront 并设置 cfRay 为各自的机场三字码完整内容 - cfRay := func() string { - if resp.Header.Get("Server") == "cloudflare" { - return resp.Header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC - } - return resp.Header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1 - }() - colo := p.getColo(cfRay) - if colo == "" { // 没有匹配到三字码或不符合指定地区则直接结束该 IP 测试 - return 0, 0 + // 判断是否匹配指定的地区码 + colo = p.filterColo(colo) + if colo == "" { // 没有匹配到地区码或不符合指定地区则直接结束该 IP 测试 + return 0, 0, "" } } - } // 循环测速计算延迟 success := 0 var delay time.Duration for i := 0; i < PingTimes; i++ { - requ, err := http.NewRequest(http.MethodHead, URL, nil) + request, err := http.NewRequest(http.MethodHead, URL, nil) if err != nil { log.Fatal("意外的错误,情报告:", err) - return 0, 0 + return 0, 0, "" } - requ.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36") + request.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36") if i == PingTimes-1 { - requ.Header.Set("Connection", "close") + request.Header.Set("Connection", "close") } startTime := time.Now() - resp, err := hc.Do(requ) + response, err := hc.Do(request) if err != nil { continue } success++ - io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() + io.Copy(io.Discard, response.Body) + _ = response.Body.Close() duration := time.Since(startTime) delay += duration - } - return success, delay - + return success, delay, colo } func MapColoMap() *sync.Map { if HttpingCFColo == "" { return nil } - // 将参数指定的地区三字码转为大写并格式化 + // 将 -cfcolo 参数指定的地区地区码转为大写并格式化 colos := strings.Split(strings.ToUpper(HttpingCFColo), ",") colomap := &sync.Map{} for _, colo := range colos { @@ -121,21 +116,36 @@ func MapColoMap() *sync.Map { return colomap } -func (p *Ping) getColo(b string) string { - if b == "" { +// 从响应头中获取 地区码 值 +func getHeaderColo(header http.Header) (colo string) { + // 如果是 Cloudflare 的服务器,则获取 CF-RAY 头部 + if header.Get("Server") == "cloudflare" { + colo = header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC + } else { // 如果是 AWS CloudFront 的服务器,则获取 X-Amz-Cf-Pop 头部 + colo = header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1 + } + + // 如果没有获取到头部信息,说明不是 Cloudflare 和 AWS CloudFront,则直接返回空字符串 + if colo == "" { return "" } - // 正则匹配并返回 机场三字码 - out := OutRegexp.FindString(b) + // 正则匹配并返回 机场地区码 + return ColoRegexp.FindString(colo) +} +// 处理地区码 +func (p *Ping) filterColo(colo string) string { + if colo == "" { + return "" + } + // 如果没有指定 -cfcolo 参数,则直接返回 if HttpingCFColomap == nil { - return out + return colo } - // 匹配 机场三字码 是否为指定的地区 - _, ok := HttpingCFColomap.Load(out) + // 匹配 机场地区码 是否为指定的地区 + _, ok := HttpingCFColomap.Load(colo) if ok { - return out + return colo } - return "" } diff --git a/task/tcping.go b/task/tcping.go index 511e688..ed6381e 100644 --- a/task/tcping.go +++ b/task/tcping.go @@ -64,9 +64,9 @@ func (p *Ping) Run() utils.PingDelaySet { return p.csv } if Httping { - fmt.Printf("开始延迟测速(模式:HTTP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate) + fmt.Printf("\033[34m开始延迟测速(模式:HTTP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\033[0m\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate) } else { - fmt.Printf("开始延迟测速(模式:TCP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate) + fmt.Printf("\033[34m开始延迟测速(模式:TCP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\033[0m\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate) } for _, ip := range p.ips { p.wg.Add(1) @@ -104,11 +104,12 @@ func (p *Ping) tcping(ip *net.IPAddr) (bool, time.Duration) { } // pingReceived pingTotalTime -func (p *Ping) checkConnection(ip *net.IPAddr) (recv int, totalDelay time.Duration) { +func (p *Ping) checkConnection(ip *net.IPAddr) (recv int, totalDelay time.Duration, colo string) { if Httping { - recv, totalDelay = p.httping(ip) + recv, totalDelay, colo = p.httping(ip) return } + colo = "" // TCPing 不获取 colo for i := 0; i < PingTimes; i++ { if ok, delay := p.tcping(ip); ok { recv++ @@ -128,7 +129,7 @@ func (p *Ping) appendIPData(data *utils.PingData) { // handle tcping func (p *Ping) tcpingHandler(ip *net.IPAddr) { - recv, totalDlay := p.checkConnection(ip) + recv, totalDlay, colo := p.checkConnection(ip) nowAble := len(p.csv) if recv != 0 { nowAble++ @@ -142,6 +143,7 @@ func (p *Ping) tcpingHandler(ip *net.IPAddr) { Sended: PingTimes, Received: recv, Delay: totalDlay / time.Duration(recv), + Colo: colo, } p.appendIPData(data) } diff --git a/utils/csv.go b/utils/csv.go index ac48e27..02aa2b1 100644 --- a/utils/csv.go +++ b/utils/csv.go @@ -23,6 +23,7 @@ var ( InputMaxLossRate = maxLossRate Output = defaultOutput PrintNum = 10 + Debug = false // 是否开启调试模式 ) // 是否打印测试结果 @@ -40,6 +41,7 @@ type PingData struct { Sended int Received int Delay time.Duration + Colo string } type CloudflareIPData struct { @@ -58,13 +60,19 @@ func (cf *CloudflareIPData) getLossRate() float32 { } func (cf *CloudflareIPData) toString() []string { - result := make([]string, 6) + result := make([]string, 7) result[0] = cf.IP.String() result[1] = strconv.Itoa(cf.Sended) result[2] = strconv.Itoa(cf.Received) result[3] = strconv.FormatFloat(float64(cf.getLossRate()), 'f', 2, 32) result[4] = strconv.FormatFloat(cf.Delay.Seconds()*1000, 'f', 2, 32) result[5] = strconv.FormatFloat(cf.DownloadSpeed/1024/1024, 'f', 2, 32) + // 如果 Colo 为空,则使用 "N/A" 表示 + if cf.Colo == "" { + result[6] = "N/A" + } else { + result[6] = cf.Colo + } return result } @@ -79,7 +87,7 @@ func ExportCsv(data []CloudflareIPData) { } defer fp.Close() w := csv.NewWriter(fp) //创建一个新的写入文件流 - _ = w.Write([]string{"IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)"}) + _ = w.Write([]string{"IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度(MB/s)", "地区码"}) _ = w.WriteAll(convertToString(data)) w.Flush() } @@ -168,18 +176,18 @@ func (s DownloadSpeedSet) Print() { if len(dateString) < PrintNum { // 如果IP数组长度(IP数量) 小于 打印次数,则次数改为IP数量 PrintNum = len(dateString) } - headFormat := "%-16s%-5s%-5s%-5s%-6s%-11s\n" - dataFormat := "%-18s%-8s%-8s%-8s%-10s%-15s\n" + headFormat := "\033[34m%-16s%-5s%-5s%-5s%-6s%-12s%-5s\033[0m\n" + dataFormat := "%-18s%-8s%-8s%-8s%-10s%-16s%-8s\n" for i := 0; i < PrintNum; i++ { // 如果要输出的 IP 中包含 IPv6,那么就需要调整一下间隔 if len(dateString[i][0]) > 15 { - headFormat = "%-40s%-5s%-5s%-5s%-6s%-11s\n" - dataFormat = "%-42s%-8s%-8s%-8s%-10s%-15s\n" + headFormat = "\033[34m%-40s%-5s%-5s%-5s%-6s%-12s%-5s\033[0m\n" + dataFormat = "%-42s%-8s%-8s%-8s%-10s%-16s%-8s\n" break } } - fmt.Printf(headFormat, "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)") + fmt.Printf(headFormat, "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度(MB/s)", "地区码") for i := 0; i < PrintNum; i++ { - fmt.Printf(dataFormat, dateString[i][0], dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5]) + fmt.Printf(dataFormat, dateString[i][0], dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5], dateString[i][6]) } if !noOutput() { fmt.Printf("\n完整测速结果已写入 %v 文件,可使用记事本/表格软件查看。\n", Output)