From 66912dd65795895d9f0e9e7fd86f5ec781671bf6 Mon Sep 17 00:00:00 2001
From: xiu2 <54703944+XIU2@users.noreply.github.com>
Date: Sun, 29 Jun 2025 00:30:05 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=9C=B0=E5=8C=BA=E7=A0=81(=E6=9C=BA?=
=?UTF-8?q?=E5=9C=BA=E4=B8=89=E5=AD=97=E7=A0=81=EF=BC=8C=E4=BB=85=E9=99=90?=
=?UTF-8?q?=20Cloudflare=E3=80=81AWS=20CloudFront=EF=BC=8CHTTPing=20?=
=?UTF-8?q?=E5=92=8C=20=E4=B8=8B=E8=BD=BD=E6=B5=8B=E9=80=9F(=E6=97=A0?=
=?UTF-8?q?=E8=AE=BA=E6=98=AF=E5=93=AA=E4=B8=AA=E6=B5=8B=E9=80=9F=E6=A8=A1?=
=?UTF-8?q?=E5=BC=8F)=E8=BF=87=E7=A8=8B=E4=B8=AD=E9=83=BD=E4=BC=9A?=
=?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=8E=B7=E5=8F=96);=20=E6=96=B0=E5=A2=9E=20?=
=?UTF-8?q?=E8=B0=83=E8=AF=95=E6=A8=A1=E5=BC=8F=E8=BF=90=E8=A1=8C=E5=8F=82?=
=?UTF-8?q?=E6=95=B0(-debug=20=E6=96=B9=E4=BE=BF=E6=8E=92=E6=9F=A5?=
=?UTF-8?q?=E4=B8=8B=E8=BD=BD=E6=B5=8B=E9=80=9F=E8=BF=87=E7=A8=8B=E4=B8=AD?=
=?UTF-8?q?=E9=81=87=E5=88=B0=E7=9A=84=E9=97=AE=E9=A2=98);=20=E6=96=B0?=
=?UTF-8?q?=E5=A2=9E=20=E5=BD=A9=E8=89=B2=E8=BE=93=E5=87=BA=E5=86=85?=
=?UTF-8?q?=E5=AE=B9;=20=E8=B0=83=E6=95=B4=20=E5=BD=93=E6=B2=A1=E6=89=BE?=
=?UTF-8?q?=E5=88=B0=E7=AC=A6=E5=90=88=E9=80=9F=E5=BA=A6=E6=9D=A1=E4=BB=B6?=
=?UTF-8?q?=E7=9A=84=20IP=20=E6=97=B6=EF=BC=8C=E9=BB=98=E8=AE=A4=E4=B8=8D?=
=?UTF-8?q?=E5=86=8D=E7=9B=B4=E6=8E=A5=E5=BF=BD=E7=95=A5=E6=9D=A1=E4=BB=B6?=
=?UTF-8?q?=E8=BE=93=E5=87=BA=E6=89=80=E6=9C=89=20IP=20=E6=B5=8B=E9=80=9F?=
=?UTF-8?q?=E7=BB=93=E6=9E=9C=E4=BA=86=EF=BC=8C=E8=80=8C=E6=98=AF=E5=8F=AA?=
=?UTF-8?q?=E6=9C=89=E5=9C=A8=E8=B0=83=E8=AF=95=E6=A8=A1=E5=BC=8F=E4=B8=8B?=
=?UTF-8?q?=E6=89=8D=E4=BC=9A=E8=BE=93=E5=87=BA;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 133 +++++++++++++++++++++++++++++++++++------------
main.go | 9 +++-
task/download.go | 37 +++++++++----
task/httping.go | 102 ++++++++++++++++++++----------------
task/tcping.go | 12 +++--
utils/csv.go | 24 ++++++---
6 files changed, 214 insertions(+), 103 deletions(-)
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)