mirror of
https://github.com/XIU2/CloudflareSpeedTest.git
synced 2026-03-07 15:15:52 +08:00
Compare commits
296 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2de42f8d30 | ||
|
|
bcbf7f0e2a | ||
|
|
f735c0f5b3 | ||
|
|
7fbe1d1b56 | ||
|
|
6eaacd6b2c | ||
|
|
b955387787 | ||
|
|
e57e9d3897 | ||
|
|
801dd53774 | ||
|
|
e03a371e19 | ||
|
|
346b27f011 | ||
|
|
4f9ca03008 | ||
|
|
d76a8bc749 | ||
|
|
3cd9a35994 | ||
|
|
574e419ee1 | ||
|
|
d66b2aa1f0 | ||
|
|
a392fddd3a | ||
|
|
2bb2f509d9 | ||
|
|
3cf898701f | ||
|
|
74267eb2ac | ||
|
|
bf1bd561da | ||
|
|
b5e741ea3e | ||
|
|
6099b48007 | ||
|
|
397cfd25d3 | ||
|
|
db40924a32 | ||
|
|
1bacd92f6a | ||
|
|
2fe4e41a9e | ||
|
|
c14627725c | ||
|
|
6c599ed1a6 | ||
|
|
e236a24a0c | ||
|
|
fc5cd11867 | ||
|
|
e9699196ef | ||
|
|
66912dd657 | ||
|
|
04da9f5659 | ||
|
|
013c27c059 | ||
|
|
c7f8170485 | ||
|
|
d6818fa60c | ||
|
|
b15fe2c899 | ||
|
|
551f753323 | ||
|
|
8acc095cd9 | ||
|
|
ee31cff5c3 | ||
|
|
b85ae8edec | ||
|
|
25fc2921e2 | ||
|
|
5e39f2612f | ||
|
|
dbceab3418 | ||
|
|
6c9d336d01 | ||
|
|
a438fc6c93 | ||
|
|
1d9d742bca | ||
|
|
209c7100ff | ||
|
|
c74168a10d | ||
|
|
66c0923bf7 | ||
|
|
bbe762c46f | ||
|
|
887a55ce77 | ||
|
|
570a710446 | ||
|
|
0033f96e5d | ||
|
|
b3036eb1ef | ||
|
|
84cbd2995c | ||
|
|
1995d088ec | ||
|
|
13f0c322a8 | ||
|
|
a5cf1fdc05 | ||
|
|
8a6670b67a | ||
|
|
e815a60867 | ||
|
|
bf832c5a4e | ||
|
|
f2e87d38d7 | ||
|
|
63234aeb98 | ||
|
|
23b4f260e7 | ||
|
|
dbb5864235 | ||
|
|
d96281af1f | ||
|
|
6d12e7bac9 | ||
|
|
9d9d71fa04 | ||
|
|
176271d5bb | ||
|
|
216d0f5f6b | ||
|
|
7534e34ae2 | ||
|
|
b3010e21fd | ||
|
|
aa52d607bc | ||
|
|
ca5564c56e | ||
|
|
2cfc1e6b69 | ||
|
|
a4b39ecc23 | ||
|
|
49992a763e | ||
|
|
ffc0dafbae | ||
|
|
4621c56370 | ||
|
|
2d400783c9 | ||
|
|
133924d169 | ||
|
|
bef4476b25 | ||
|
|
6c698a815d | ||
|
|
5f9d28e4a0 | ||
|
|
4222b6ce95 | ||
|
|
7c36abc12f | ||
|
|
eae7d2eead | ||
|
|
b6f3ddcd4c | ||
|
|
c6d49bc7cb | ||
|
|
a07af2f58b | ||
|
|
9f000aa23a | ||
|
|
1800c2e89e | ||
|
|
39d1687875 | ||
|
|
24a162ac46 | ||
|
|
3125b49b63 | ||
|
|
729631deef | ||
|
|
fe0721f077 | ||
|
|
201619ecf7 | ||
|
|
12effc301f | ||
|
|
fede8a04e6 | ||
|
|
b63f368837 | ||
|
|
c9a8c2b8cf | ||
|
|
e80d7fff50 | ||
|
|
f61a94886b | ||
|
|
f3f2bafbe7 | ||
|
|
5cf3b4bb42 | ||
|
|
6667e82c84 | ||
|
|
af4e8cf8b2 | ||
|
|
f2ec0a447c | ||
|
|
45b604103e | ||
|
|
05ba005960 | ||
|
|
a0b194f1d0 | ||
|
|
7e21b2f46b | ||
|
|
efafc38db8 | ||
|
|
b14b3657b7 | ||
|
|
2634fe43ff | ||
|
|
b26fa89447 | ||
|
|
a6b08bb4b8 | ||
|
|
bd6fbb9f1f | ||
|
|
465a83462b | ||
|
|
c045b914a4 | ||
|
|
d8db541c1f | ||
|
|
65018cd415 | ||
|
|
f5ce273688 | ||
|
|
ed1d512d65 | ||
|
|
00d3e21e1b | ||
|
|
2662b1affb | ||
|
|
fa1940fe91 | ||
|
|
0a9fb30671 | ||
|
|
741f080c54 | ||
|
|
2c4d115b83 | ||
|
|
bd16488cfb | ||
|
|
a1ae4f8e45 | ||
|
|
f4f1fdcd80 | ||
|
|
562789aa15 | ||
|
|
c54fb92537 | ||
|
|
07ef3fbb8f | ||
|
|
12066eec6c | ||
|
|
7079a6c7b7 | ||
|
|
a2fa9dadb3 | ||
|
|
f47ba5d06f | ||
|
|
93430a08ef | ||
|
|
295af17fc8 | ||
|
|
25e8ad170b | ||
|
|
b6e5438ec1 | ||
|
|
5b7791b892 | ||
|
|
0beea5df99 | ||
|
|
882678e1eb | ||
|
|
ffb2e54a7b | ||
|
|
154cf5564d | ||
|
|
c6449f6a4a | ||
|
|
021914f975 | ||
|
|
ce22f6b2a4 | ||
|
|
81c6a70a5c | ||
|
|
ce15d4741a | ||
|
|
32b5afaa88 | ||
|
|
73362a1d4c | ||
|
|
82cfd0123f | ||
|
|
a6f627f5fb | ||
|
|
12695563d7 | ||
|
|
d8316525ff | ||
|
|
3c8b51db36 | ||
|
|
1ed7fb7726 | ||
|
|
e8c847d2c9 | ||
|
|
eaaaa53c10 | ||
|
|
270dcab04b | ||
|
|
c1ba72b3a3 | ||
|
|
01b105d42d | ||
|
|
e3c0a3a742 | ||
|
|
7f6e374ae4 | ||
|
|
b22e7fd4af | ||
|
|
2e20a7c279 | ||
|
|
195f95ed80 | ||
|
|
8c720eb137 | ||
|
|
d412f8645d | ||
|
|
04ed493e9d | ||
|
|
794c387b50 | ||
|
|
20708746c3 | ||
|
|
cace4572f1 | ||
|
|
80c3c7bdad | ||
|
|
a6f72ade0a | ||
|
|
0f721fc44b | ||
|
|
8d8a8df123 | ||
|
|
1173bcda06 | ||
|
|
ce6b494996 | ||
|
|
a24d393a11 | ||
|
|
01d796fb90 | ||
|
|
78513f977a | ||
|
|
9878cb56d4 | ||
|
|
aeb1474e5a | ||
|
|
c88577a183 | ||
|
|
60c9902177 | ||
|
|
b2422ca12d | ||
|
|
91e6ae5add | ||
|
|
a08c2c35c4 | ||
|
|
6629fdbdea | ||
|
|
81de6c28ed | ||
|
|
70ce3f8cbb | ||
|
|
f8230942a3 | ||
|
|
638e5ffaba | ||
|
|
533b140c9c | ||
|
|
3895479b35 | ||
|
|
8264138dd8 | ||
|
|
6cb94db59e | ||
|
|
85923cf335 | ||
|
|
60631a958f | ||
|
|
ea46fc5e04 | ||
|
|
9471094d27 | ||
|
|
be4634ee9d | ||
|
|
e5bbd07aea | ||
|
|
445ca36877 | ||
|
|
f6288f4e52 | ||
|
|
cfe30c2f6a | ||
|
|
115bd2af03 | ||
|
|
272eb40d5b | ||
|
|
2c46cfcd0f | ||
|
|
8eea8cdd0e | ||
|
|
f82425bbb6 | ||
|
|
2fa023c7f3 | ||
|
|
ed0a8bbfea | ||
|
|
7301d32cbe | ||
|
|
ab6390a4a4 | ||
|
|
fdbf9ca131 | ||
|
|
49c14f3e0e | ||
|
|
80eadc8df2 | ||
|
|
fbaa312622 | ||
|
|
48012f513a | ||
|
|
28d5d89e85 | ||
|
|
f1a9b5c966 | ||
|
|
4d64abb94d | ||
|
|
71671ebe66 | ||
|
|
83c63e975e | ||
|
|
6f74e60444 | ||
|
|
921bb5ed62 | ||
|
|
337de75d11 | ||
|
|
169af2afe9 | ||
|
|
ccfca867d3 | ||
|
|
897977a11d | ||
|
|
7212110dde | ||
|
|
d651fdeb54 | ||
|
|
d453124943 | ||
|
|
e92badf8be | ||
|
|
01dc5df491 | ||
|
|
b428775cc5 | ||
|
|
5202f73108 | ||
|
|
1bb45af3f1 | ||
|
|
3736d81dda | ||
|
|
a42059737b | ||
|
|
46da45b25f | ||
|
|
acd47ee96d | ||
|
|
698108f453 | ||
|
|
2963b61910 | ||
|
|
9bab2944b1 | ||
|
|
fa5e4f34f0 | ||
|
|
db9d092010 | ||
|
|
180097b044 | ||
|
|
c115249811 | ||
|
|
3b7851f77c | ||
|
|
80c201f160 | ||
|
|
85546abb23 | ||
|
|
1d46334a6b | ||
|
|
afd8736268 | ||
|
|
17ff85954f | ||
|
|
7ece9d6cda | ||
|
|
d67b9bc86d | ||
|
|
73ae874645 | ||
|
|
0441c27cec | ||
|
|
7e5804b7ba | ||
|
|
cee772547b | ||
|
|
1a939f752b | ||
|
|
5b45f400a2 | ||
|
|
3b43b21b83 | ||
|
|
2f284efddd | ||
|
|
f960ce4a4b | ||
|
|
8ae1d495af | ||
|
|
3b957cb1a4 | ||
|
|
faee85edb8 | ||
|
|
b901003dd1 | ||
|
|
873200de80 | ||
|
|
ac29c9666e | ||
|
|
a540789180 | ||
|
|
012865f02d | ||
|
|
74c1744ca6 | ||
|
|
e66d44882e | ||
|
|
0d3af2d5f8 | ||
|
|
70d11e3bc1 | ||
|
|
ec650d3084 | ||
|
|
4038a5b0aa | ||
|
|
1ff4c24c59 | ||
|
|
ffbb161f79 | ||
|
|
3efabb5661 | ||
|
|
e2f23aeb48 | ||
|
|
97bbb8b5e9 | ||
|
|
5d75cb861e | ||
|
|
eed4f4fa0b |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
script/*.* linguist-language=None
|
||||
34
.github/ISSUE_TEMPLATE/01-bugReport.yml
vendored
Normal file
34
.github/ISSUE_TEMPLATE/01-bugReport.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: 反馈问题 (Bug report)
|
||||
description: 软件报错等异常情况,或遇到预期之外的问题...
|
||||
labels: 反馈问题
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
发之前,请先搜下有没有类似的 [**Issues**](https://github.com/XIU2/CloudflareSpeedTest/issues)、[**Discussions**](https://github.com/XIU2/CloudflareSpeedTest/discussions) 问题(包括[**已关闭**](https://github.com/XIU2/CloudflareSpeedTest/issues?q=is%3Aissue+is%3Aclosed)的),请勿重复发起!
|
||||
|
||||
> [!NOTE]
|
||||
> 注意!如果你反馈的问题和 CFST **软件本身功能无关**(如关于 Cloudflare CDN 的网络问题),请**前往讨论区** (💬 [**Discussions**](https://github.com/XIU2/CloudflareSpeedTest/discussions)) 发帖交流!(不合适的 Issues 会被转过去
|
||||
****
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述
|
||||
description: 必填,最好写上 复现问题 的步骤,越详细越好,特别是一些复杂的问题
|
||||
placeholder: 请输入...
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 软件版本
|
||||
description: 必填,可通过运行软件来获取版本信息(例如 v2.2.2)
|
||||
placeholder: 请输入...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: 附加截图
|
||||
description: 可选,也可以是一些错误代码
|
||||
placeholder: 可在此粘贴图片,或点击下方 [Attach files by dragging & dropping, selecting or pasting them.] 文字来选择图片...
|
||||
31
.github/ISSUE_TEMPLATE/02-featureRequest.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/02-featureRequest.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: 功能建议 (Feature request)
|
||||
description: 有什么建议,或希望添加、完善某个功能...
|
||||
labels: 功能建议
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
发之前,请先搜下有没有类似的 [**Issues**](https://github.com/XIU2/CloudflareSpeedTest/issues)、[**Discussions**](https://github.com/XIU2/CloudflareSpeedTest/discussions) 问题(包括[**已关闭**](https://github.com/XIU2/CloudflareSpeedTest/issues?q=is%3Aissue+is%3Aclosed)的),请勿重复发起!
|
||||
|
||||
> [!NOTE]
|
||||
> 注意!如果你提的功能建议和 CFST **软件本身功能无关**,请**前往讨论区** (💬 [**Discussions**](https://github.com/XIU2/CloudflareSpeedTest/discussions)) 发帖交流!
|
||||
|
||||
> [!TIP]
|
||||
> 另外,不接受**个性化**的小众功能请求(即 **很少人** 或 **只有你自己** 才会用到的功能)
|
||||
****
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能需求
|
||||
description: 必填,你要什么样的功能?
|
||||
placeholder: 请输入...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: anticipation
|
||||
attributes:
|
||||
label: 预期目标
|
||||
description: 必填,你希望该功能具体是什么样子的?如果能提供 示例/截图/代码 就更好了
|
||||
placeholder: 请输入...
|
||||
validations:
|
||||
required: true
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 前往讨论区 (💬 Discussions)
|
||||
url: https://github.com/XIU2/CloudflareSpeedTest/discussions
|
||||
about: Issues 仅用于对 CFST 本身的反馈问题、功能建议,其他话题请到 💬 Discussions 发帖讨论(不合适的 Issues 会被转过去
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
dist
|
||||
Releases
|
||||
*.exe
|
||||
*.csv
|
||||
|
||||
167
IPRangeLoader.go
167
IPRangeLoader.go
@@ -1,167 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 根据子网掩码获取主机数量
|
||||
func getCidrHostNum(maskLen int) int {
|
||||
cidrIPNum := int(0)
|
||||
if maskLen < 32 {
|
||||
var i int = int(32 - maskLen - 1)
|
||||
for ; i >= 1; i-- {
|
||||
cidrIPNum += 1 << i
|
||||
}
|
||||
cidrIPNum += 2
|
||||
} else {
|
||||
cidrIPNum = 1
|
||||
}
|
||||
if cidrIPNum > 255 {
|
||||
cidrIPNum = 255
|
||||
}
|
||||
return cidrIPNum
|
||||
}
|
||||
|
||||
// 获取 IP 最后一段最小值和最大值
|
||||
func getCidrIPRange(cidr string) (uint8, uint8) {
|
||||
ip := strings.Split(cidr, "/")[0]
|
||||
ipSegs := strings.Split(ip, ".")
|
||||
maskLen, _ := strconv.Atoi(strings.Split(cidr, "/")[1])
|
||||
seg4MinIP, seg4MaxIP := getIPSeg4Range(ipSegs, maskLen)
|
||||
//ipPrefix := ipSegs[0] + "." + ipSegs[1] + "." + ipSegs[2] + "."
|
||||
|
||||
return seg4MinIP,
|
||||
seg4MaxIP
|
||||
}
|
||||
|
||||
// 获取 IP 最后一段的区间
|
||||
func getIPSeg4Range(ipSegs []string, maskLen int) (uint8, uint8) {
|
||||
ipSeg, _ := strconv.Atoi(ipSegs[3])
|
||||
return getIPSegRange(uint8(ipSeg), uint8(32-maskLen))
|
||||
}
|
||||
|
||||
// 根据输入的基础IP地址和CIDR掩码计算一个IP片段的区间
|
||||
func getIPSegRange(userSegIP, offset uint8) (uint8, uint8) {
|
||||
var ipSegMax uint8 = 255
|
||||
netSegIP := ipSegMax << offset
|
||||
segMinIP := netSegIP & userSegIP
|
||||
segMaxIP := userSegIP&(255<<offset) | ^(255 << offset)
|
||||
return uint8(segMinIP), uint8(segMaxIP)
|
||||
}
|
||||
|
||||
func loadFirstIPOfRangeFromFile(ipFile string) []net.IPAddr {
|
||||
file, err := os.Open(ipFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
firstIPs := make([]net.IPAddr, 0)
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
IPString := scanner.Text()
|
||||
firstIP, IPRange, err := net.ParseCIDR(IPString)
|
||||
//fmt.Println(firstIP)
|
||||
//fmt.Println(IPRange)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if !ipv6Mode { // IPv4
|
||||
minIP, maxIP := getCidrIPRange(scanner.Text()) // 获取 IP 最后一段最小值和最大值
|
||||
Mask, _ := strconv.Atoi(strings.Split(scanner.Text(), "/")[1]) // 获取子网掩码
|
||||
MaxIPNum := getCidrHostNum(Mask) // 根据子网掩码获取主机数量
|
||||
for IPRange.Contains(firstIP) {
|
||||
if allip { // 如果是测速全部 IP
|
||||
for i := int(minIP); i <= int(maxIP); i++ { // 遍历 IP 最后一段最小值到最大值
|
||||
firstIP[15] = uint8(i)
|
||||
firstIPCopy := make([]byte, len(firstIP))
|
||||
copy(firstIPCopy, firstIP)
|
||||
firstIPs = append(firstIPs, net.IPAddr{IP: firstIPCopy})
|
||||
}
|
||||
} else { // 随机 IP 的最后一段 0.0.0.X
|
||||
firstIP[15] = minIP + randipEndWith(MaxIPNum)
|
||||
firstIPCopy := make([]byte, len(firstIP))
|
||||
copy(firstIPCopy, firstIP)
|
||||
firstIPs = append(firstIPs, net.IPAddr{IP: firstIPCopy})
|
||||
}
|
||||
firstIP[14]++ // 0.0.(X+1).X
|
||||
if firstIP[14] == 0 {
|
||||
firstIP[13]++ // 0.(X+1).X.X
|
||||
if firstIP[13] == 0 {
|
||||
firstIP[12]++ // (X+1).X.X.X
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { //IPv6
|
||||
var tempIP uint8
|
||||
for IPRange.Contains(firstIP) {
|
||||
//fmt.Println(firstIP)
|
||||
//fmt.Println(firstIP[0], firstIP[1], firstIP[2], firstIP[3], firstIP[4], firstIP[5], firstIP[6], firstIP[7], firstIP[8], firstIP[9], firstIP[10], firstIP[11], firstIP[12], firstIP[13], firstIP[14], firstIP[15])
|
||||
firstIP[15] = randipEndWith(255) // 随机 IP 的最后一段
|
||||
firstIP[14] = randipEndWith(255) // 随机 IP 的最后一段
|
||||
firstIPCopy := make([]byte, len(firstIP))
|
||||
copy(firstIPCopy, firstIP)
|
||||
firstIPs = append(firstIPs, net.IPAddr{IP: firstIPCopy})
|
||||
tempIP = firstIP[13]
|
||||
firstIP[13] += randipEndWith(255)
|
||||
if firstIP[13] < tempIP {
|
||||
tempIP = firstIP[12]
|
||||
firstIP[12] += randipEndWith(255)
|
||||
if firstIP[12] < tempIP {
|
||||
tempIP = firstIP[11]
|
||||
firstIP[11] += randipEndWith(255)
|
||||
if firstIP[11] < tempIP {
|
||||
tempIP = firstIP[10]
|
||||
firstIP[10] += randipEndWith(255)
|
||||
if firstIP[10] < tempIP {
|
||||
tempIP = firstIP[9]
|
||||
firstIP[9] += randipEndWith(255)
|
||||
if firstIP[9] < tempIP {
|
||||
tempIP = firstIP[8]
|
||||
firstIP[8] += randipEndWith(255)
|
||||
if firstIP[8] < tempIP {
|
||||
tempIP = firstIP[7]
|
||||
firstIP[7] += randipEndWith(255)
|
||||
if firstIP[7] < tempIP {
|
||||
tempIP = firstIP[6]
|
||||
firstIP[6] += randipEndWith(255)
|
||||
if firstIP[6] < tempIP {
|
||||
tempIP = firstIP[5]
|
||||
firstIP[5] += randipEndWith(255)
|
||||
if firstIP[5] < tempIP {
|
||||
tempIP = firstIP[4]
|
||||
firstIP[4] += randipEndWith(255)
|
||||
if firstIP[4] < tempIP {
|
||||
tempIP = firstIP[3]
|
||||
firstIP[3] += randipEndWith(255)
|
||||
if firstIP[3] < tempIP {
|
||||
tempIP = firstIP[2]
|
||||
firstIP[2] += randipEndWith(255)
|
||||
if firstIP[2] < tempIP {
|
||||
tempIP = firstIP[1]
|
||||
firstIP[1] += randipEndWith(255)
|
||||
if firstIP[1] < tempIP {
|
||||
tempIP = firstIP[0]
|
||||
firstIP[0] += randipEndWith(255)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return firstIPs
|
||||
}
|
||||
17
go.mod
17
go.mod
@@ -1,8 +1,17 @@
|
||||
module CloudflareSpeedTest
|
||||
module github.com/XIU2/CloudflareSpeedTest
|
||||
|
||||
go 1.14
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/VividCortex/ewma v1.1.1
|
||||
github.com/cheggaaa/pb/v3 v3.0.4
|
||||
github.com/VividCortex/ewma v1.2.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.7
|
||||
github.com/fatih/color v1.18.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
)
|
||||
|
||||
35
go.sum
35
go.sum
@@ -1,17 +1,18 @@
|
||||
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
|
||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||
github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM=
|
||||
github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI=
|
||||
github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
||||
17
ip.txt
17
ip.txt
@@ -1,7 +1,3 @@
|
||||
1.1.1.0/24
|
||||
1.0.0.0/24
|
||||
1.1.1.1/32
|
||||
1.0.0.1/32
|
||||
173.245.48.0/20
|
||||
103.21.244.0/22
|
||||
103.22.200.0/22
|
||||
@@ -14,5 +10,16 @@
|
||||
198.41.128.0/17
|
||||
162.158.0.0/15
|
||||
104.16.0.0/12
|
||||
172.64.0.0/13
|
||||
172.64.0.0/17
|
||||
172.64.128.0/18
|
||||
172.64.192.0/19
|
||||
172.64.224.0/22
|
||||
172.64.229.0/24
|
||||
172.64.230.0/23
|
||||
172.64.232.0/21
|
||||
172.64.240.0/21
|
||||
172.64.248.0/21
|
||||
172.65.0.0/16
|
||||
172.66.0.0/16
|
||||
172.67.0.0/16
|
||||
131.0.72.0/22
|
||||
139
ipv6.txt
139
ipv6.txt
@@ -1,42 +1,97 @@
|
||||
2606:4700:10::6814:0/112
|
||||
2606:4700:10::ac43:0/112
|
||||
2606:4700:3000::/48
|
||||
2606:4700:3001::/48
|
||||
2606:4700:3002::/48
|
||||
2606:4700:3003::/48
|
||||
2606:4700:3004::/48
|
||||
2606:4700:3005::/48
|
||||
2606:4700:3006::/48
|
||||
2606:4700:3007::/48
|
||||
2606:4700:3008::/48
|
||||
2606:4700:3009::/48
|
||||
2606:4700:3010::/48
|
||||
2606:4700:3011::/48
|
||||
2606:4700:3012::/48
|
||||
2606:4700:3013::/48
|
||||
2606:4700:3014::/48
|
||||
2606:4700:3015::/48
|
||||
2606:4700:3016::/48
|
||||
2606:4700:3017::/48
|
||||
2606:4700:3018::/48
|
||||
2606:4700:3019::/48
|
||||
2606:4700:3020::/48
|
||||
2606:4700:3021::/48
|
||||
2606:4700:3022::/48
|
||||
2606:4700:3023::/48
|
||||
2606:4700:3024::/48
|
||||
2606:4700:3025::/48
|
||||
2606:4700:3026::/48
|
||||
2606:4700:3027::/48
|
||||
2606:4700:3028::/48
|
||||
2606:4700:3029::/48
|
||||
2606:4700:3030::/48
|
||||
2606:4700:3031::/48
|
||||
2606:4700:3032::/48
|
||||
2606:4700:3033::/48
|
||||
2606:4700:3034::/48
|
||||
2606:4700:3035::/48
|
||||
2606:4700:3036::/48
|
||||
2606:4700:3037::/48
|
||||
2606:4700:3038::/48
|
||||
2606:4700:3039::/48
|
||||
2400:cb00:2049::/48
|
||||
2400:cb00:f00e::/48
|
||||
2606:4700::/32
|
||||
2606:4700:10::/48
|
||||
2606:4700:130::/48
|
||||
2606:4700:3000::/48
|
||||
2606:4700:3001::/48
|
||||
2606:4700:3002::/48
|
||||
2606:4700:3003::/48
|
||||
2606:4700:3004::/48
|
||||
2606:4700:3005::/48
|
||||
2606:4700:3006::/48
|
||||
2606:4700:3007::/48
|
||||
2606:4700:3008::/48
|
||||
2606:4700:3009::/48
|
||||
2606:4700:3010::/48
|
||||
2606:4700:3011::/48
|
||||
2606:4700:3012::/48
|
||||
2606:4700:3013::/48
|
||||
2606:4700:3014::/48
|
||||
2606:4700:3015::/48
|
||||
2606:4700:3016::/48
|
||||
2606:4700:3017::/48
|
||||
2606:4700:3018::/48
|
||||
2606:4700:3019::/48
|
||||
2606:4700:3020::/48
|
||||
2606:4700:3021::/48
|
||||
2606:4700:3022::/48
|
||||
2606:4700:3023::/48
|
||||
2606:4700:3024::/48
|
||||
2606:4700:3025::/48
|
||||
2606:4700:3026::/48
|
||||
2606:4700:3027::/48
|
||||
2606:4700:3028::/48
|
||||
2606:4700:3029::/48
|
||||
2606:4700:3030::/48
|
||||
2606:4700:3031::/48
|
||||
2606:4700:3032::/48
|
||||
2606:4700:3033::/48
|
||||
2606:4700:3034::/48
|
||||
2606:4700:3035::/48
|
||||
2606:4700:3036::/48
|
||||
2606:4700:3037::/48
|
||||
2606:4700:3038::/48
|
||||
2606:4700:3039::/48
|
||||
2606:4700:a0::/48
|
||||
2606:4700:a1::/48
|
||||
2606:4700:a8::/48
|
||||
2606:4700:a9::/48
|
||||
2606:4700:a::/48
|
||||
2606:4700:b::/48
|
||||
2606:4700:c::/48
|
||||
2606:4700:d0::/48
|
||||
2606:4700:d1::/48
|
||||
2606:4700:d::/48
|
||||
2606:4700:e0::/48
|
||||
2606:4700:e1::/48
|
||||
2606:4700:e2::/48
|
||||
2606:4700:e3::/48
|
||||
2606:4700:e4::/48
|
||||
2606:4700:e5::/48
|
||||
2606:4700:e6::/48
|
||||
2606:4700:e7::/48
|
||||
2606:4700:e::/48
|
||||
2606:4700:f1::/48
|
||||
2606:4700:f2::/48
|
||||
2606:4700:f3::/48
|
||||
2606:4700:f4::/48
|
||||
2606:4700:f5::/48
|
||||
2606:4700:f::/48
|
||||
2803:f800:50::/48
|
||||
2803:f800:51::/48
|
||||
2a06:98c1:3100::/48
|
||||
2a06:98c1:3101::/48
|
||||
2a06:98c1:3102::/48
|
||||
2a06:98c1:3103::/48
|
||||
2a06:98c1:3104::/48
|
||||
2a06:98c1:3105::/48
|
||||
2a06:98c1:3106::/48
|
||||
2a06:98c1:3107::/48
|
||||
2a06:98c1:3108::/48
|
||||
2a06:98c1:3109::/48
|
||||
2a06:98c1:310a::/48
|
||||
2a06:98c1:310b::/48
|
||||
2a06:98c1:310c::/48
|
||||
2a06:98c1:310d::/48
|
||||
2a06:98c1:310e::/48
|
||||
2a06:98c1:310f::/48
|
||||
2a06:98c1:3120::/48
|
||||
2a06:98c1:3121::/48
|
||||
2a06:98c1:3122::/48
|
||||
2a06:98c1:3123::/48
|
||||
2a06:98c1:3200::/48
|
||||
2a06:98c1:50::/48
|
||||
2a06:98c1:51::/48
|
||||
2a06:98c1:54::/48
|
||||
2a06:98c1:58::/48
|
||||
447
main.go
447
main.go
@@ -1,271 +1,176 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cheggaaa/pb/v3"
|
||||
)
|
||||
|
||||
var version, ipFile, outputFile, versionNew string
|
||||
var disableDownload, ipv6Mode, allip bool
|
||||
var tcpPort, printResultNum, timeLimit, speedLimit, downloadSecond int
|
||||
|
||||
func init() {
|
||||
var printVersion bool
|
||||
var help = `
|
||||
CloudflareSpeedTest ` + version + `
|
||||
测试 Cloudflare CDN 所有 IP 的延迟和速度,获取最快 IP (IPv4+IPv6)!
|
||||
https://github.com/XIU2/CloudflareSpeedTest
|
||||
|
||||
参数:
|
||||
-n 500
|
||||
测速线程数量;越多测速越快,性能弱的设备 (如路由器) 请适当调低;(默认 500 最多 1000)
|
||||
-t 4
|
||||
延迟测速次数;单个 IP 延迟测速次数,为 1 时将过滤丢包的IP,TCP协议;(默认 4)
|
||||
-tp 443
|
||||
延迟测速端口;延迟测速 TCP 协议的端口;(默认 443)
|
||||
-dn 20
|
||||
下载测速数量;延迟测速并排序后,从最低延迟起下载测速的数量;(默认 20)
|
||||
-dt 10
|
||||
下载测速时间;单个 IP 下载测速最长时间,单位:秒;(默认 10)
|
||||
-url https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png
|
||||
下载测速地址;用来下载测速的 Cloudflare CDN 文件地址,如地址含有空格请加上引号;
|
||||
-tl 200
|
||||
平均延迟上限;只输出低于指定平均延迟的 IP,与下载速度下限搭配使用;(默认 9999 ms)
|
||||
-sl 5
|
||||
下载速度下限;只输出高于指定下载速度的 IP,凑够指定数量 [-dn] 才会停止测速;(默认 0 MB/s)
|
||||
-p 20
|
||||
显示结果数量;测速后直接显示指定数量的结果,为 0 时不显示结果直接退出;(默认 20)
|
||||
-f ip.txt
|
||||
IP段数据文件;如路径含有空格请加上引号;支持其他 CDN IP段;(默认 ip.txt)
|
||||
-o result.csv
|
||||
输出结果文件;如路径含有空格请加上引号;值为空格时不输出 [-o " "];(默认 result.csv)
|
||||
-dd
|
||||
禁用下载测速;禁用后测速结果会按延迟排序 (默认按下载速度排序);(默认 启用)
|
||||
-ipv6
|
||||
IPv6测速模式;确保 IP 段数据文件内只包含 IPv6 IP段,软件不支持同时测速 IPv4+IPv6;(默认 IPv4)
|
||||
-allip
|
||||
测速全部的IP;对 IP 段中的每个 IP (仅支持 IPv4) 进行测速;(默认 每个 IP 段随机测速一个 IP)
|
||||
-v
|
||||
打印程序版本+检查版本更新
|
||||
-h
|
||||
打印帮助说明
|
||||
`
|
||||
|
||||
flag.IntVar(&pingRoutine, "n", 500, "测速线程数量")
|
||||
flag.IntVar(&pingTime, "t", 4, "延迟测速次数")
|
||||
flag.IntVar(&tcpPort, "tp", 443, "延迟测速端口")
|
||||
flag.IntVar(&downloadTestCount, "dn", 20, "下载测速数量")
|
||||
flag.IntVar(&downloadSecond, "dt", 10, "下载测速时间")
|
||||
flag.StringVar(&url, "url", "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png", "下载测速地址")
|
||||
flag.IntVar(&timeLimit, "tl", 9999, "延迟时间上限")
|
||||
flag.IntVar(&speedLimit, "sl", 0, "下载速度下限")
|
||||
flag.IntVar(&printResultNum, "p", 20, "显示结果数量")
|
||||
flag.BoolVar(&disableDownload, "dd", false, "禁用下载测速")
|
||||
flag.BoolVar(&ipv6Mode, "ipv6", false, "禁用下载测速")
|
||||
flag.BoolVar(&allip, "allip", false, "测速全部 IP")
|
||||
flag.StringVar(&ipFile, "f", "ip.txt", "IP 数据文件")
|
||||
flag.StringVar(&outputFile, "o", "result.csv", "输出结果文件")
|
||||
flag.BoolVar(&printVersion, "v", false, "打印程序版本")
|
||||
|
||||
flag.Usage = func() { fmt.Print(help) }
|
||||
flag.Parse()
|
||||
if printVersion {
|
||||
println(version)
|
||||
fmt.Println("检查版本更新中...")
|
||||
checkUpdate()
|
||||
if versionNew != "" {
|
||||
fmt.Println("发现新版本 [" + versionNew + "]!请前往 [https://github.com/XIU2/CloudflareSpeedTest] 更新!")
|
||||
} else {
|
||||
fmt.Println("当前为最新版本 [" + version + "]!")
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
if pingRoutine <= 0 {
|
||||
pingRoutine = 500
|
||||
}
|
||||
if pingTime <= 0 {
|
||||
pingTime = 4
|
||||
}
|
||||
if tcpPort < 1 || tcpPort > 65535 {
|
||||
tcpPort = 443
|
||||
}
|
||||
if downloadTestCount <= 0 {
|
||||
downloadTestCount = 20
|
||||
}
|
||||
if downloadSecond <= 0 {
|
||||
downloadSecond = 10
|
||||
}
|
||||
if url == "" {
|
||||
url = "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png"
|
||||
}
|
||||
if timeLimit <= 0 {
|
||||
timeLimit = 9999
|
||||
}
|
||||
if speedLimit < 0 {
|
||||
speedLimit = 0
|
||||
}
|
||||
if printResultNum < 0 {
|
||||
printResultNum = 20
|
||||
}
|
||||
if ipFile == "" {
|
||||
ipFile = "ip.txt"
|
||||
}
|
||||
if outputFile == " " {
|
||||
outputFile = ""
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go checkUpdate() // 检查版本更新
|
||||
initRandSeed() // 置随机数种子
|
||||
failTime = pingTime // 设置接收次数
|
||||
ips := loadFirstIPOfRangeFromFile(ipFile) // 读入IP
|
||||
pingCount := len(ips) * pingTime // 计算进度条总数(IP*测试次数)
|
||||
bar := pb.Simple.Start(pingCount) // 进度条总数
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var data = make([]CloudflareIPData, 0)
|
||||
var data_2 = make([]CloudflareIPData, 0)
|
||||
downloadTestTime = time.Duration(downloadSecond) * time.Second
|
||||
|
||||
fmt.Println("# XIU2/CloudflareSpeedTest " + version + "\n")
|
||||
if ipv6Mode {
|
||||
fmt.Println("开始延迟测速(模式:TCP IPv6,端口:" + strconv.Itoa(tcpPort) + "):")
|
||||
} else {
|
||||
fmt.Println("开始延迟测速(模式:TCP IPv4,端口:" + strconv.Itoa(tcpPort) + "):")
|
||||
}
|
||||
control := make(chan bool, pingRoutine)
|
||||
for _, ip := range ips {
|
||||
wg.Add(1)
|
||||
control <- false
|
||||
handleProgress := handleProgressGenerator(bar) // 多线程进度条
|
||||
go tcpingGoroutine(&wg, &mu, ip, tcpPort, pingTime, &data, control, handleProgress)
|
||||
}
|
||||
wg.Wait()
|
||||
bar.Finish()
|
||||
|
||||
sort.Sort(CloudflareIPDataSet(data)) // 排序
|
||||
|
||||
// 下载测速
|
||||
if !disableDownload { // 如果禁用下载测速就跳过
|
||||
if len(data) > 0 { // IP数组长度(IP数量) 大于 0 时继续
|
||||
if len(data) < downloadTestCount { // 如果IP数组长度(IP数量) 小于 下载测速次数,则次数改为IP数
|
||||
//fmt.Println("\n[信息] IP 数量小于下载测速次数(" + strconv.Itoa(downloadTestCount) + " < " + strconv.Itoa(len(data)) + "),下载测速次数改为IP数。\n")
|
||||
downloadTestCount = len(data)
|
||||
}
|
||||
var downloadTestCount_2 int // 临时的下载测速次数
|
||||
if timeLimit == 9999 && speedLimit == 0 {
|
||||
downloadTestCount_2 = downloadTestCount // 如果没有指定条件,则临时变量为下载测速次数
|
||||
fmt.Println("开始下载测速:")
|
||||
} else if timeLimit > 0 || speedLimit >= 0 {
|
||||
downloadTestCount_2 = len(data) // 如果指定了任意一个条件,则临时变量改为总数量
|
||||
fmt.Println("开始下载测速(延迟时间上限:" + strconv.Itoa(timeLimit) + " ms,下载速度下限:" + strconv.Itoa(speedLimit) + " MB/s):")
|
||||
}
|
||||
bar = pb.Simple.Start(downloadTestCount)
|
||||
for i := 0; i < downloadTestCount_2; i++ {
|
||||
_, speed := DownloadSpeedHandler(data[i].ip)
|
||||
data[i].downloadSpeed = speed
|
||||
if int(data[i].pingTime) <= timeLimit && int(float64(speed)/1024/1024) >= speedLimit {
|
||||
data_2 = append(data_2, data[i]) // 延迟和速度均满足条件时,添加到新数组中
|
||||
bar.Add(1)
|
||||
if len(data_2) == downloadTestCount { // 满足条件的 IP =下载测速次数,则跳出循环
|
||||
break
|
||||
}
|
||||
} else if int(data[i].pingTime) > timeLimit {
|
||||
break
|
||||
}
|
||||
}
|
||||
bar.Finish()
|
||||
} else {
|
||||
fmt.Println("\n[信息] IP数量为 0,跳过下载测速。")
|
||||
}
|
||||
}
|
||||
|
||||
if len(data_2) > 0 { // 如果该数字有内容,说明进行过指定条件的下载测速
|
||||
sort.Sort(CloudflareIPDataSetD(data_2)) // 排序
|
||||
if outputFile != "" {
|
||||
ExportCsv(outputFile, data_2) // 输出结果到文件(指定延迟时间或下载速度的)
|
||||
}
|
||||
printResult(data_2) // 显示最快结果(指定延迟时间或下载速度的)
|
||||
} else {
|
||||
if outputFile != "" {
|
||||
ExportCsv(outputFile, data) // 输出结果到文件
|
||||
}
|
||||
printResult(data) // 显示最快结果
|
||||
}
|
||||
}
|
||||
|
||||
// 显示最快结果
|
||||
func printResult(data []CloudflareIPData) {
|
||||
sysType := runtime.GOOS
|
||||
if printResultNum > 0 { // 如果禁止直接输出结果就跳过
|
||||
dateString := convertToString(data) // 转为多维数组 [][]String
|
||||
if len(dateString) > 0 { // IP数组长度(IP数量) 大于 0 时继续
|
||||
if len(dateString) < printResultNum { // 如果IP数组长度(IP数量) 小于 打印次数,则次数改为IP数量
|
||||
//fmt.Println("\n[信息] IP 数量小于显示结果数量(" + strconv.Itoa(printResultNum) + " < " + strconv.Itoa(len(dateString)) + "),显示结果数量改为IP数量。\n")
|
||||
printResultNum = len(dateString)
|
||||
}
|
||||
if ipv6Mode { // IPv6 太长了,所以需要调整一下间隔
|
||||
fmt.Printf("%-40s%-5s%-5s%-5s%-6s%-11s\n", "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)")
|
||||
for i := 0; i < printResultNum; i++ {
|
||||
fmt.Printf("%-42s%-8s%-8s%-8s%-10s%-15s\n", ipPadding(dateString[i][0]), dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5])
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("%-16s%-5s%-5s%-5s%-6s%-11s\n", "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)")
|
||||
for i := 0; i < printResultNum; i++ {
|
||||
fmt.Printf("%-18s%-8s%-8s%-8s%-10s%-15s\n", ipPadding(dateString[i][0]), dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5])
|
||||
}
|
||||
}
|
||||
|
||||
if versionNew != "" {
|
||||
fmt.Println("\n发现新版本 [" + versionNew + "]!请前往 [https://github.com/XIU2/CloudflareSpeedTest] 更新!")
|
||||
}
|
||||
|
||||
if sysType == "windows" { // 如果是 Windows 系统,则需要按下 回车键 或 Ctrl+C 退出
|
||||
if outputFile != "" {
|
||||
fmt.Printf("\n完整测速结果已写入 %v 文件,请使用记事本/表格软件查看。\n按下 回车键 或 Ctrl+C 退出。", outputFile)
|
||||
} else {
|
||||
fmt.Printf("\n按下 回车键 或 Ctrl+C 退出。")
|
||||
}
|
||||
var pause int
|
||||
fmt.Scanln(&pause)
|
||||
} else { // 其它系统直接退出
|
||||
if outputFile != "" {
|
||||
fmt.Println("\n完整测速结果已写入 " + outputFile + " 文件,请使用记事本/表格软件查看。")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("\n[信息] IP数量为 0,跳过输出结果。")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("\n完整测速结果已写入 " + outputFile + " 文件,请使用记事本/表格软件查看。")
|
||||
}
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
func checkUpdate() {
|
||||
timeout := time.Duration(10 * time.Second)
|
||||
client := http.Client{Timeout: timeout}
|
||||
res, err := client.Get("https://api.xiuer.pw/ver/cloudflarespeedtest.txt")
|
||||
if err == nil {
|
||||
// 读取资源数据 body: []byte
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
// 关闭资源流
|
||||
res.Body.Close()
|
||||
if err == nil {
|
||||
if string(body) != version {
|
||||
versionNew = string(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/XIU2/CloudflareSpeedTest/task"
|
||||
"github.com/XIU2/CloudflareSpeedTest/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
version, versionNew string
|
||||
)
|
||||
|
||||
func init() {
|
||||
var printVersion bool
|
||||
var help = `
|
||||
CloudflareSpeedTest ` + version + `
|
||||
测试各个 CDN 或网站所有 IP 的延迟和速度,获取最快 IP (IPv4+IPv6)!
|
||||
https://github.com/XIU2/CloudflareSpeedTest
|
||||
|
||||
参数:
|
||||
-n 200
|
||||
延迟测速线程;越多延迟测速越快,性能弱的设备 (如路由器) 请勿太高;(默认 200 最多 1000)
|
||||
-t 4
|
||||
延迟测速次数;单个 IP 延迟测速的次数;(默认 4 次)
|
||||
-dn 10
|
||||
下载测速数量;延迟测速并排序后,从最低延迟起下载测速的数量;(默认 10 个)
|
||||
-dt 10
|
||||
下载测速时间;单个 IP 下载测速最长时间,不能太短;(默认 10 秒)
|
||||
-tp 443
|
||||
指定测速端口;延迟测速/下载测速时使用的端口;(默认 443 端口)
|
||||
-url https://cf.xiu2.xyz/url
|
||||
指定测速地址;延迟测速(HTTPing)/下载测速时使用的地址,默认地址不保证可用性,建议自建;
|
||||
|
||||
-httping
|
||||
切换测速模式;延迟测速模式改为 HTTP 协议,所用测试地址为 [-url] 参数;(默认 TCPing)
|
||||
-httping-code 200
|
||||
有效状态代码;HTTPing 延迟测速时网页返回的有效 HTTP 状态码,仅限一个;(默认 200 301 302)
|
||||
-cfcolo HKG,KHH,NRT,LAX,SEA,SJC,FRA,MAD
|
||||
匹配指定地区;IATA 机场地区码或国家/城市码,英文逗号分隔,仅 HTTPing 模式可用;(默认 所有地区)
|
||||
|
||||
-tl 200
|
||||
平均延迟上限;只输出低于指定平均延迟的 IP,各上下限条件可搭配使用;(默认 9999 ms)
|
||||
-tll 40
|
||||
平均延迟下限;只输出高于指定平均延迟的 IP;(默认 0 ms)
|
||||
-tlr 0.2
|
||||
丢包几率上限;只输出低于/等于指定丢包率的 IP,范围 0.00~1.00,0 过滤掉任何丢包的 IP;(默认 1.00)
|
||||
-sl 5
|
||||
下载速度下限;只输出高于指定下载速度的 IP,凑够指定数量 [-dn] 才会停止测速;(默认 0.00 MB/s)
|
||||
|
||||
-p 10
|
||||
显示结果数量;测速后直接显示指定数量的结果,为 0 时不显示结果直接退出;(默认 10 个)
|
||||
-f ip.txt
|
||||
IP段数据文件;如路径含有空格请加上引号;支持其他 CDN IP段;(默认 ip.txt)
|
||||
-ip 1.1.1.1,2.2.2.2/24,2606:4700::/32
|
||||
指定IP段数据;直接通过参数指定要测速的 IP 段数据,英文逗号分隔;(默认 空)
|
||||
-o result.csv
|
||||
写入结果文件;如路径含有空格请加上引号;值为空时不写入文件 [-o ""];(默认 result.csv)
|
||||
|
||||
-dd
|
||||
禁用下载测速;禁用后测速结果会按延迟排序 (默认按下载速度排序);(默认 启用)
|
||||
-allip
|
||||
测速全部的IP;对 IP 段中的每个 IP (仅支持 IPv4) 进行测速;(默认 每个 /24 段随机测速一个 IP)
|
||||
|
||||
-debug
|
||||
调试输出模式;会在一些非预期情况下输出更多日志以便判断原因;(默认 关闭)
|
||||
|
||||
-v
|
||||
打印程序版本 + 检查版本更新
|
||||
-h
|
||||
打印帮助说明
|
||||
`
|
||||
var minDelay, maxDelay, downloadTime int
|
||||
var maxLossRate float64
|
||||
flag.IntVar(&task.Routines, "n", 200, "延迟测速线程")
|
||||
flag.IntVar(&task.PingTimes, "t", 4, "延迟测速次数")
|
||||
flag.IntVar(&task.TestCount, "dn", 10, "下载测速数量")
|
||||
flag.IntVar(&downloadTime, "dt", 10, "下载测速时间")
|
||||
flag.IntVar(&task.TCPPort, "tp", 443, "指定测速端口")
|
||||
flag.StringVar(&task.URL, "url", "https://cf.xiu2.xyz/url", "指定测速地址")
|
||||
|
||||
flag.BoolVar(&task.Httping, "httping", false, "切换测速模式")
|
||||
flag.IntVar(&task.HttpingStatusCode, "httping-code", 0, "有效状态代码")
|
||||
flag.StringVar(&task.HttpingCFColo, "cfcolo", "", "匹配指定地区")
|
||||
|
||||
flag.IntVar(&maxDelay, "tl", 9999, "平均延迟上限")
|
||||
flag.IntVar(&minDelay, "tll", 0, "平均延迟下限")
|
||||
flag.Float64Var(&maxLossRate, "tlr", 1, "丢包几率上限")
|
||||
flag.Float64Var(&task.MinSpeed, "sl", 0, "下载速度下限")
|
||||
|
||||
flag.IntVar(&utils.PrintNum, "p", 10, "显示结果数量")
|
||||
flag.StringVar(&task.IPFile, "f", "ip.txt", "IP段数据文件")
|
||||
flag.StringVar(&task.IPText, "ip", "", "指定IP段数据")
|
||||
flag.StringVar(&utils.Output, "o", "result.csv", "输出结果文件")
|
||||
|
||||
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 {
|
||||
utils.Yellow.Println("[提示] 在使用 [-sl] 参数时,建议搭配 [-tl] 参数,以避免因凑不够 [-dn] 数量而一直测速...")
|
||||
}
|
||||
utils.InputMaxDelay = time.Duration(maxDelay) * time.Millisecond
|
||||
utils.InputMinDelay = time.Duration(minDelay) * time.Millisecond
|
||||
utils.InputMaxLossRate = float32(maxLossRate)
|
||||
task.Timeout = time.Duration(downloadTime) * time.Second
|
||||
task.HttpingCFColomap = task.MapColoMap()
|
||||
|
||||
if printVersion {
|
||||
println(version)
|
||||
fmt.Println("检查版本更新中...")
|
||||
checkUpdate()
|
||||
if versionNew != "" {
|
||||
utils.Yellow.Printf("*** 发现新版本 [%s]!请前往 [https://github.com/XIU2/CloudflareSpeedTest] 更新! ***", versionNew)
|
||||
} else {
|
||||
utils.Green.Println("当前为最新版本 [" + version + "]!")
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
task.InitRandSeed() // 置随机数种子
|
||||
|
||||
fmt.Printf("# XIU2/CloudflareSpeedTest %s \n\n", version)
|
||||
|
||||
// 开始延迟测速 + 过滤延迟/丢包
|
||||
pingData := task.NewPing().Run().FilterDelay().FilterLossRate()
|
||||
// 开始下载测速
|
||||
speedData := task.TestDownloadSpeed(pingData)
|
||||
utils.ExportCsv(speedData) // 输出文件
|
||||
speedData.Print() // 打印结果
|
||||
endPrint() // 根据情况选择退出方式(针对 Windows)
|
||||
}
|
||||
|
||||
// 根据情况选择退出方式(针对 Windows)
|
||||
func endPrint() {
|
||||
if utils.NoPrintResult() { // 如果不需要打印测速结果,则直接退出
|
||||
return
|
||||
}
|
||||
if runtime.GOOS == "windows" { // 如果是 Windows 系统,则需要按下 回车键 或 Ctrl+C 退出(避免通过双击运行时,测速完毕后直接关闭)
|
||||
fmt.Printf("按下 回车键 或 Ctrl+C 退出。")
|
||||
fmt.Scanln()
|
||||
}
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
func checkUpdate() {
|
||||
timeout := 10 * time.Second
|
||||
client := http.Client{Timeout: timeout}
|
||||
res, err := client.Get("https://api.xiu2.xyz/ver/cloudflarespeedtest.txt")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 读取资源数据 body: []byte
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 关闭资源流
|
||||
defer res.Body.Close()
|
||||
if string(body) != version {
|
||||
versionNew = string(body)
|
||||
}
|
||||
}
|
||||
|
||||
178
script/README.md
Normal file
178
script/README.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# XIU2/CloudflareSpeedTest - Script(脚本)
|
||||
|
||||
这里都是一些通过调用 **CFST** 并**扩展实现更多个性化功能**的脚本。
|
||||
|
||||
****
|
||||
> [!TIP]
|
||||
> 我之所以将 CFST 制作为一个**命令行程序**,就是考虑到**通用性**,因为毕竟不可能把所有需求都塞到软件内(特别是一些**个性化、小众**的需求),这样增加维护难度和精力不说,还会导致软件异常臃肿(`“变成我讨厌的样子”`),而命令行程序的优势之一就在于**可以很方便的和其他软件、脚本搭配使用**。
|
||||
|
||||
比如像下面这些我写的几个脚本,就是把一些需求以外置脚本方式实现。
|
||||
|
||||
> 即脚本调用 CFST 测速并获取结果,然后***按照自己的需求自由决定***如何处理得到的测速结果(比如修改 Hosts 等)。
|
||||
|
||||
总的来说,我写的这几个脚本都比较简单,功能也很单一,除了满足部分用户的需求外,***更像是一个 CFST 与脚本搭配使用的示例参考***,对于一些会写脚本、软件的用户来说,完全可以**自给自足**来实现一些个性化需求。
|
||||
|
||||
当然,如果你有一些自用好用的脚本也可以通过 [**Issues**](https://github.com/XIU2/CloudflareSpeedTest/issues)、[**Discussions**](https://github.com/XIU2/CloudflareSpeedTest/discussions) 或 **Pull requests** 发给我添加到这里让更多人用到!
|
||||
|
||||
> 小提示:点击↗右上角的三横杠图标按钮即可查看目录~
|
||||
|
||||
****
|
||||
## 📑 cfst_hosts.sh / cfst_hosts.bat (已内置压缩包)
|
||||
|
||||
脚本会运行 CFST 获得最快 IP,并替换掉 Hosts 文件中的旧 CDN IP。
|
||||
|
||||
> **作者:**[@XIU2](https://github.com/xiu2)
|
||||
> **使用说明/问题反馈:https://github.com/XIU2/CloudflareSpeedTest/discussions/312**
|
||||
|
||||
<details>
|
||||
<summary><code><strong>「 更新日志」</strong></code></summary>
|
||||
|
||||
****
|
||||
|
||||
#### 2025年12月15日,版本 v1.0.5 (cfst_hosts.bat)
|
||||
- **1. 修复** CFST新版本下获取不到第一行 IP 的问题
|
||||
|
||||
#### 2021年12月17日,版本 v1.0.4
|
||||
- **1. 优化** [找不到满足条件的 IP 就一直循环测速] 功能,在指定下载测速下限时没有重新测速的问题(默认注释)
|
||||
|
||||
#### 2021年12月17日,版本 v1.0.3
|
||||
- **1. 新增** 找不到满足条件的 IP 就一直循环测速功能(默认注释)
|
||||
- **2. 优化** 代码
|
||||
|
||||
#### 2021年09月29日,版本 v1.0.2
|
||||
- **1. 修复** 当测速结果 IP 数量为 0 时,脚本没有退出的问题
|
||||
|
||||
#### 2021年04月29日,版本 v1.0.1
|
||||
- **1. 优化** 不再需要加上 -p 0 参数来避免回车键退出了(现在可以即显示结果,又不用担心回车键退出程序)
|
||||
|
||||
#### 2021年01月28日,版本 v1.0.0
|
||||
- **1. 发布** 第一个版本
|
||||
|
||||
</details>
|
||||
|
||||
****
|
||||
|
||||
## 📑 cfst_3proxy.bat (已内置压缩包)
|
||||
|
||||
脚本会运行 CFST 测速后获取最快 IP 并替换 3Proxy 配置文件中的旧 Cloudflare CDN IP。
|
||||
可以把所有 Cloudflare CDN IP 都重定向至最快 IP,实现一劳永逸的加速所有使用 Cloudflare CDN 的网站(不需要一个个添加域名到 Hosts 了)。
|
||||
|
||||
> **作者:**[@XIU2](https://github.com/xiu2)
|
||||
> **使用说明/问题反馈:https://github.com/XIU2/CloudflareSpeedTest/discussions/71**
|
||||
|
||||
<details>
|
||||
<summary><code><strong>「 更新日志」</strong></code></summary>
|
||||
|
||||
****
|
||||
|
||||
#### 2025年12月15日,版本 v1.0.6
|
||||
- **1. 修复** CFST新版本下获取不到第一行 IP 的问题
|
||||
|
||||
#### 2021年12月17日,版本 v1.0.5
|
||||
- **1. 优化** [找不到满足条件的 IP 就一直循环测速] 功能,在指定下载测速下限时没有重新测速的问题(默认注释)
|
||||
|
||||
#### 2021年12月17日,版本 v1.0.4
|
||||
- **1. 新增** 找不到满足条件的 IP 就一直循环测速功能(默认注释)
|
||||
- **2. 优化** 代码
|
||||
|
||||
#### 2021年09月29日,版本 v1.0.3
|
||||
- **1. 修复** 当测速结果 IP 数量为 0 时,脚本没有退出的问题
|
||||
|
||||
#### 2021年04月29日,版本 v1.0.2
|
||||
- **1. 优化** 不再需要加上 -p 0 参数来避免回车键退出了(现在可以即显示结果,又不用担心回车键退出程序)
|
||||
|
||||
#### 2021年03月16日,版本 v1.0.1
|
||||
- **1. 优化** 代码及注释内容
|
||||
|
||||
#### 2021年03月13日,版本 v1.0.0
|
||||
- **1. 发布** 第一个版本
|
||||
|
||||
</details>
|
||||
|
||||
****
|
||||
|
||||
## 📑 cfst_dnspod.sh
|
||||
|
||||
如果你的域名托管在 **dnspod**,则可以通过 dnspod 官方提供的 API 来自动更新域名解析记录!
|
||||
脚本会运行 CFST 测速获得最快 IP,并通过 Cloudflare API 来更新域名解析记录为这个最快 IP。
|
||||
|
||||
> **作者:**[@imashen](https://github.com/imashen)
|
||||
> **使用说明/问题反馈:https://github.com/XIU2/CloudflareSpeedTest/pull/533**
|
||||
|
||||
<details>
|
||||
<summary><code><strong>「 更新日志」</strong></code></summary>
|
||||
|
||||
****
|
||||
|
||||
#### 2024年08月06日,版本 v1.0.0
|
||||
- **1. 发布** 第一个版本
|
||||
|
||||
</details>
|
||||
|
||||
****
|
||||
|
||||
## 📑 cfst_ddns.sh / cfst_ddns.bat
|
||||
|
||||
如果你的域名托管在 **Cloudflare**,则可以通过 Cloudflare 官方提供的 API 来自动更新域名解析记录!
|
||||
脚本会运行 CFST 测速获得最快 IP,并通过 Cloudflare API 来更新域名解析记录为这个最快 IP。
|
||||
|
||||
> **作者:**[@XIU2](https://github.com/xiu2)
|
||||
> **使用说明/问题反馈:https://github.com/XIU2/CloudflareSpeedTest/discussions/481**
|
||||
|
||||
<details>
|
||||
<summary><code><strong>「 更新日志」</strong></code></summary>
|
||||
|
||||
****
|
||||
|
||||
#### 2025年12月15日,版本 v1.0.6 (cfst_ddns.bat)
|
||||
- **1. 修复** CFST新版本下获取不到第一行 IP 的问题
|
||||
|
||||
#### 2024年10月06日,版本 v1.0.5
|
||||
- **1. 新增** 支持 API 令牌方式(相比 API 密钥这种全局权限的,API 令牌可以自由控制权限)
|
||||
|
||||
#### 2021年12月17日,版本 v1.0.4
|
||||
- **1. 新增** 找不到满足条件的 IP 就一直循环测速功能(默认注释)
|
||||
- **2. 优化** 代码
|
||||
|
||||
#### 2021年09月29日,版本 v1.0.3
|
||||
- **1. 修复** 当测速结果 IP 数量为 0 时,脚本没有退出的问题
|
||||
|
||||
#### 2021年04月29日,版本 v1.0.2
|
||||
- **1. 优化** 不再需要加上 -p 0 参数来避免回车键退出了(现在可以即显示结果,又不用担心回车键退出程序)
|
||||
|
||||
#### 2021年01月27日,版本 v1.0.1
|
||||
- **1. 优化** 配置从文件中读取
|
||||
|
||||
#### 2021年01月26日,版本 v1.0.0
|
||||
- **1. 发布** 第一个版本
|
||||
|
||||
</details>
|
||||
|
||||
****
|
||||
|
||||
## 📑 cfst_dnsmasq.sh
|
||||
|
||||
脚本会运行 CFST 测速后获取最快 IP 并替换 dnsmasq 配置文件中的旧 Cloudflare CDN IP。
|
||||
|
||||
> **作者:**[@Sving1024](https://github.com/Sving1024)
|
||||
> **使用说明/问题反馈:https://github.com/XIU2/CloudflareSpeedTest/discussions/566**
|
||||
|
||||
<details>
|
||||
<summary><code><strong>「 更新日志」</strong></code></summary>
|
||||
|
||||
****
|
||||
|
||||
#### 2025年01月22日,版本 v1.0.1
|
||||
- **1. 修复** IPv6 的问题
|
||||
|
||||
#### 2024年12月28日,版本 v1.0.0
|
||||
- **1. 发布** 第一个版本
|
||||
|
||||
</details>
|
||||
|
||||
****
|
||||
|
||||
## 功能建议/问题反馈
|
||||
|
||||
如果这些脚本使用过程中你遇到了什么问题,可以先去脚本对应的 **`使用说明`** 帖子里看看是否有别人问过了。
|
||||
如果没找到类似问题,那么就在脚本对应的 **`使用说明`** 帖子里直接评论问作者吧。
|
||||
134
script/cfst_3proxy.bat
Normal file
134
script/cfst_3proxy.bat
Normal file
@@ -0,0 +1,134 @@
|
||||
:: --------------------------------------------------------------
|
||||
:: <09><>Ŀ: CloudflareSpeedTest <20>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD> 3Proxy
|
||||
:: <09>汾: 1.0.6
|
||||
:: <09><><EFBFBD><EFBFBD>: XIU2
|
||||
:: <09><>Ŀ: https://github.com/XIU2/CloudflareSpeedTest
|
||||
:: --------------------------------------------------------------
|
||||
@echo off
|
||||
Setlocal Enabledelayedexpansion
|
||||
|
||||
::<3A>ж<EFBFBD><D0B6>Ƿ<EFBFBD><C7B7>ѻ<EFBFBD><D1BB>ù<EFBFBD><C3B9><EFBFBD>ԱȨ<D4B1><C8A8>
|
||||
|
||||
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
|
||||
|
||||
if '%errorlevel%' NEQ '0' (
|
||||
goto UACPrompt
|
||||
) else ( goto gotAdmin )
|
||||
|
||||
::д<><D0B4> vbs <20>ű<EFBFBD><C5B1>Թ<EFBFBD><D4B9><EFBFBD>Ա<EFBFBD><D4B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><D0B1>ű<EFBFBD><C5B1><EFBFBD>bat<61><74>
|
||||
|
||||
:UACPrompt
|
||||
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
|
||||
echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"
|
||||
"%temp%\getadmin.vbs"
|
||||
exit /B
|
||||
|
||||
::<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ vbs <20>ű<EFBFBD><C5B1><EFBFBD><EFBFBD>ڣ<EFBFBD><DAA3><EFBFBD>ɾ<EFBFBD><C9BE>
|
||||
|
||||
:gotAdmin
|
||||
if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )
|
||||
pushd "%CD%"
|
||||
CD /D "%~dp0"
|
||||
|
||||
|
||||
::<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD><D0B6>Ƿ<EFBFBD><C7B7>Ի<EFBFBD><D4BB>ù<EFBFBD><C3B9><EFBFBD>ԱȨ<D4B1>ޣ<EFBFBD><DEA3><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>о<EFBFBD>ȥ<EFBFBD><C8A5>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>DZ<EFBFBD><C7B1>ű<EFBFBD><C5B1><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD>
|
||||
|
||||
|
||||
::<3A><><EFBFBD><EFBFBD> nowip_3proxy.txt <20>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD>˵<EFBFBD><CBB5><EFBFBD>ǵ<EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD>иýű<C3BD>
|
||||
if not exist "nowip_3proxy.txt" (
|
||||
echo <20>ýű<C3BD><C5B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ CFST <20><><EFBFBD>ٺ<EFBFBD><D9BA><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD> IP <20><><EFBFBD>滻 3Proxy <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>е<EFBFBD> Cloudflare CDN IP<49><50>
|
||||
echo <20><><EFBFBD><EFBFBD><D4B0><EFBFBD><EFBFBD><EFBFBD> Cloudflare CDN IP <20><><EFBFBD>ض<EFBFBD><D8B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP<49><50>ʵ<EFBFBD><CAB5>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD>ݵļ<DDB5><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9> Cloudflare CDN <20><><EFBFBD><EFBFBD>վ<EFBFBD><D5BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫһ<D2AA><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Hosts <20>ˣ<EFBFBD><CBA3><EFBFBD>
|
||||
echo ʹ<><CAB9>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD>Ķ<EFBFBD><C4B6><EFBFBD>https://github.com/XIU2/CloudflareSpeedTest/discussions/71
|
||||
echo.
|
||||
set /p nowip="<EFBFBD><EFBFBD><EFBFBD>뵱ǰ 3Proxy <20><><EFBFBD><EFBFBD>ʹ<EFBFBD>õ<EFBFBD> Cloudflare CDN IP <20><><EFBFBD>س<EFBFBD><D8B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>ò<EFBFBD><C3B2>裩:"
|
||||
echo !nowip!>nowip_3proxy.txt
|
||||
echo.
|
||||
)
|
||||
|
||||
::<3A><> nowip_3proxy.txt <20>ļ<EFBFBD><C4BC><EFBFBD>ȡ<EFBFBD><C8A1>ǰʹ<C7B0>õ<EFBFBD> Cloudflare CDN IP
|
||||
set /p nowip=<nowip_3proxy.txt
|
||||
echo <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>...
|
||||
|
||||
|
||||
:: <20><><EFBFBD><EFBFBD> RESET <20>Ǹ<EFBFBD><C7B8><EFBFBD>Ҫ "<22>Ҳ<EFBFBD><D2B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><>һֱѭ<D6B1><D1AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȥ" <20><><EFBFBD>ܵ<EFBFBD><DCB5><EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD>
|
||||
:: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܾͰ<DCBE><CDB0><EFBFBD><EFBFBD><EFBFBD> 3 <20><> goto :STOP <20><>Ϊ goto :RESET <20><><EFBFBD><EFBFBD>
|
||||
:RESET
|
||||
|
||||
|
||||
:: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD>ӡ<EFBFBD><D3A1><EFBFBD> CFST <20><><EFBFBD><EFBFBD><EFBFBD>в<EFBFBD><D0B2><EFBFBD><EFBFBD><EFBFBD>echo.| <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6>س<EFBFBD><D8B3>˳<EFBFBD><CBB3><EFBFBD><EFBFBD><EFBFBD><F2A3A8B2><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD> -p 0 <20><><EFBFBD><EFBFBD><EFBFBD>ˣ<EFBFBD>
|
||||
echo.|cfst.exe -o "result_3proxy.txt"
|
||||
|
||||
|
||||
:: <20>жϽ<D0B6><CFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD>ڣ<EFBFBD><DAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ 0
|
||||
if not exist result_3proxy.txt (
|
||||
echo.
|
||||
echo CFST <20><><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD> IP <20><><EFBFBD><EFBFBD>Ϊ 0<><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>沽<EFBFBD><E6B2BD>...
|
||||
goto :STOP
|
||||
)
|
||||
|
||||
:: <20><>ȡ<EFBFBD><C8A1>һ<EFBFBD>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD> IP
|
||||
for /f "skip=1 tokens=1 delims=," %%i in ('more result_3proxy.txt') do (
|
||||
SET bestip=%%i
|
||||
goto :END
|
||||
)
|
||||
|
||||
:END
|
||||
|
||||
:: <20>жϸոջ<D5B8>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20>Ƿ<EFBFBD>Ϊ<EFBFBD>գ<EFBFBD><D5A3>Լ<EFBFBD><D4BC>Ƿ<EFBFBD><C7B7>;<EFBFBD> IP һ<><D2BB>
|
||||
if "%bestip%"=="" (
|
||||
echo.
|
||||
echo CFST <20><><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD> IP <20><><EFBFBD><EFBFBD>Ϊ 0<><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>沽<EFBFBD><E6B2BD>...
|
||||
goto :STOP
|
||||
)
|
||||
if "%bestip%"=="%nowip%" (
|
||||
echo.
|
||||
echo CFST <20><><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD> IP <20><><EFBFBD><EFBFBD>Ϊ 0<><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>沽<EFBFBD><E6B2BD>...
|
||||
goto :STOP
|
||||
)
|
||||
|
||||
|
||||
:: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD><EFBFBD> "<22>Ҳ<EFBFBD><D2B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><>һֱѭ<D6B1><D1AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȥ" <20><><EFBFBD><EFBFBD>Ҫ<EFBFBD>Ĵ<EFBFBD><C4B4><EFBFBD>
|
||||
:: <20><><EFBFBD>ǵ<EFBFBD><C7B5><EFBFBD>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٶ<EFBFBD><D9B6><EFBFBD><EFBFBD>ޣ<EFBFBD><DEA3><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><>û<EFBFBD>ҵ<EFBFBD>ʱ<EFBFBD><CAB1>CFST <20>ͻ<EFBFBD><CDBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><><EFBFBD><EFBFBD>
|
||||
:: <20><><EFBFBD>˵<EFBFBD><CBB5><EFBFBD>ָ<EFBFBD><D6B8> -sl <20><><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>Ҫ<EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4>뿪ͷ<EBBFAA><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD> :: ð<><C3B0>ע<EFBFBD>ͷ<EFBFBD><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>жϣ<D0B6><CFA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>10 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ 11<31><31>
|
||||
::set /a v=0
|
||||
::for /f %%a in ('type result_3proxy.txt') do set /a v+=1
|
||||
::if %v% GTR 11 (
|
||||
:: echo.
|
||||
:: echo CFST <20><><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD>û<EFBFBD><C3BB><EFBFBD>ҵ<EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP<49><50><EFBFBD><EFBFBD><EFBFBD>²<EFBFBD><C2B2><EFBFBD>...
|
||||
:: goto :RESET
|
||||
::)
|
||||
|
||||
|
||||
echo %bestip%>nowip_3proxy.txt
|
||||
echo.
|
||||
echo <20><> IP Ϊ %nowip%
|
||||
echo <20><> IP Ϊ %bestip%
|
||||
|
||||
|
||||
|
||||
:: <20>뽫<EFBFBD><EBBDAB><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD> D:\Program Files\3Proxy <20><>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD> 3Proxy <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ¼
|
||||
CD /d "D:\Program Files\3Proxy"
|
||||
:: <20><>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD>иýű<C3BD>ǰ<EFBFBD><C7B0><EFBFBD>Ѿ<EFBFBD><D1BE><EFBFBD><EFBFBD>Թ<EFBFBD> 3Proxy <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>в<EFBFBD>ʹ<EFBFBD>ã<EFBFBD>
|
||||
|
||||
|
||||
|
||||
echo.
|
||||
echo <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD> 3proxy.cfg <20>ļ<EFBFBD><C4BC><EFBFBD>3proxy.cfg_backup<75><70>...
|
||||
copy 3proxy.cfg 3proxy.cfg_backup
|
||||
echo.
|
||||
echo <20><>ʼ<EFBFBD>滻...
|
||||
(
|
||||
for /f "tokens=*" %%i in (3proxy.cfg_backup) do (
|
||||
set s=%%i
|
||||
set s=!s:%nowip%=%bestip%!
|
||||
echo !s!
|
||||
)
|
||||
)>3proxy.cfg
|
||||
|
||||
net stop 3proxy
|
||||
net start 3proxy
|
||||
|
||||
echo <20><><EFBFBD><EFBFBD>...
|
||||
echo.
|
||||
:STOP
|
||||
pause
|
||||
42
script/cfst_ddns.bat
Normal file
42
script/cfst_ddns.bat
Normal file
@@ -0,0 +1,42 @@
|
||||
:: --------------------------------------------------------------
|
||||
:: 项目: CloudflareSpeedTest 自动更新域名解析记录
|
||||
:: 版本: 1.0.6
|
||||
:: 作者: XIU2
|
||||
:: 项目: https://github.com/XIU2/CloudflareSpeedTest
|
||||
:: --------------------------------------------------------------
|
||||
@echo off
|
||||
Setlocal Enabledelayedexpansion
|
||||
|
||||
:: 这里可以自己添加、修改 CFST 的运行参数,echo.| 的作用是自动回车退出程序(不再需要加上 -p 0 参数了)
|
||||
echo.|cfst.exe -o "result_ddns.txt"
|
||||
|
||||
:: 判断结果文件是否存在,如果不存在说明结果为 0
|
||||
if not exist result_ddns.txt (
|
||||
echo.
|
||||
echo CFST 测速结果 IP 数量为 0,跳过下面步骤...
|
||||
goto :END
|
||||
)
|
||||
|
||||
for /f "skip=1 tokens=1 delims=," %%i in (result_ddns.txt) do (
|
||||
Echo %%i
|
||||
if "%%i"=="" (
|
||||
echo.
|
||||
echo CFST 测速结果 IP 数量为 0,跳过下面步骤...
|
||||
goto :END
|
||||
)
|
||||
:: API 密钥方式(全局权限)
|
||||
curl -X PUT "https://api.cloudflare.com/client/v4/zones/域名ID/dns_records/域名解析记录ID" ^
|
||||
-H "X-Auth-Email: 账号邮箱" ^
|
||||
-H "X-Auth-Key: 前面获取的 API 密钥" ^
|
||||
-H "Content-Type: application/json" ^
|
||||
--data "{\"type\":\"A\",\"name\":\"完整域名\",\"content\":\"%%i\",\"ttl\":1,\"proxied\":true}"
|
||||
:: API 令牌方式(自定义权限),如果要使用这种方式,可以把上面的删除或注释,然后把下面的行首 "::" 注释符删除即可。
|
||||
:: curl -X PUT "https://api.cloudflare.com/client/v4/zones/域名ID/dns_records/域名解析记录ID" ^
|
||||
:: -H "Authorization: Bearer 前面获取的 API 令牌" ^
|
||||
:: -H "Content-Type: application/json" ^
|
||||
:: --data "{\"type\":\"A\",\"name\":\"完整域名\",\"content\":\"%%i\",\"ttl\":1,\"proxied\":true}"
|
||||
|
||||
goto :END
|
||||
)
|
||||
:END
|
||||
pause
|
||||
65
script/cfst_ddns.sh
Normal file
65
script/cfst_ddns.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
export PATH
|
||||
# --------------------------------------------------------------
|
||||
# 项目: CloudflareSpeedTest 自动更新域名解析记录
|
||||
# 版本: 1.0.5
|
||||
# 作者: XIU2
|
||||
# 项目: https://github.com/XIU2/CloudflareSpeedTest
|
||||
# --------------------------------------------------------------
|
||||
|
||||
_READ() {
|
||||
[[ ! -e "cfst_ddns.conf" ]] && echo -e "[错误] 配置文件不存在 [cfst_ddns.conf] !" && exit 1
|
||||
CONFIG=$(cat "cfst_ddns.conf")
|
||||
FOLDER=$(echo "${CONFIG}"|grep 'FOLDER='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${FOLDER}" ]] && echo -e "[错误] 缺少配置项 [FOLDER] !" && exit 1
|
||||
ZONE_ID=$(echo "${CONFIG}"|grep 'ZONE_ID='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${ZONE_ID}" ]] && echo -e "[错误] 缺少配置项 [ZONE_ID] !" && exit 1
|
||||
DNS_RECORDS_ID=$(echo "${CONFIG}"|grep 'DNS_RECORDS_ID='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${DNS_RECORDS_ID}" ]] && echo -e "[错误] 缺少配置项 [DNS_RECORDS_ID] !" && exit 1
|
||||
KEY=$(echo "${CONFIG}"|grep 'KEY='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${KEY}" ]] && echo -e "[错误] 缺少配置项 [KEY] !" && exit 1
|
||||
EMAIL=$(echo "${CONFIG}"|grep 'EMAIL='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${EMAIL}" ]] && echo -e "[信息] 缺少配置项 [EMAIL],由 [API 密钥] 方式转为 [API 令牌] 方式!"
|
||||
TYPE=$(echo "${CONFIG}"|grep 'TYPE='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${TYPE}" ]] && echo -e "[错误] 缺少配置项 [TYPE] !" && exit 1
|
||||
NAME=$(echo "${CONFIG}"|grep 'NAME='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${NAME}" ]] && echo -e "[错误] 缺少配置项 [NAME] !" && exit 1
|
||||
TTL=$(echo "${CONFIG}"|grep 'TTL='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${TTL}" ]] && echo -e "[错误] 缺少配置项 [TTL] !" && exit 1
|
||||
PROXIED=$(echo "${CONFIG}"|grep 'PROXIED='|awk -F '=' '{print $NF}')
|
||||
[[ -z "${PROXIED}" ]] && echo -e "[错误] 缺少配置项 [PROXIED] !" && exit 1
|
||||
}
|
||||
|
||||
_UPDATE() {
|
||||
# 这里可以自己添加、修改 CFST 的运行参数
|
||||
./cfst -o "result_ddns.txt"
|
||||
|
||||
# 判断结果文件是否存在,如果不存在说明结果为 0
|
||||
[[ ! -e "result_ddns.txt" ]] && echo "CFST 测速结果 IP 数量为 0,跳过下面步骤..." && exit 0
|
||||
|
||||
CONTENT=$(sed -n "2,1p" result_ddns.txt | awk -F, '{print $1}')
|
||||
if [[ -z "${CONTENT}" ]]; then
|
||||
echo "CFST 测速结果 IP 数量为 0,跳过下面步骤..."
|
||||
exit 0
|
||||
fi
|
||||
# 如果 EMAIL 变量是空的,那么就代表要使用 API 令牌方式
|
||||
if [[ -n "${EMAIL}" ]]; then
|
||||
# API 密钥方式(全局权限)
|
||||
curl -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${DNS_RECORDS_ID}" \
|
||||
-H "X-Auth-Email: ${EMAIL}" \
|
||||
-H "X-Auth-Key: ${KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"type\":\"${TYPE}\",\"name\":\"${NAME}\",\"content\":\"${CONTENT}\",\"ttl\":${TTL},\"proxied\":${PROXIED}}"
|
||||
else
|
||||
# API 令牌方式(自定义权限)
|
||||
curl -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${DNS_RECORDS_ID}" \
|
||||
-H "Authorization: Bearer ${KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"type\":\"${TYPE}\",\"name\":\"${NAME}\",\"content\":\"${CONTENT}\",\"ttl\":${TTL},\"proxied\":${PROXIED}}"
|
||||
fi
|
||||
}
|
||||
|
||||
_READ
|
||||
cd "${FOLDER}"
|
||||
_UPDATE
|
||||
56
script/cfst_dnsmasq.sh
Executable file
56
script/cfst_dnsmasq.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
export PATH
|
||||
# --------------------------------------------------------------
|
||||
# 项目: CloudflareSpeedTest 自动更新 dnsmasq 配置文件
|
||||
# 版本: 1.0.1
|
||||
# 作者: XIU2,Sving1024
|
||||
# 项目: https://github.com/XIU2/CloudflareSpeedTest
|
||||
# --------------------------------------------------------------
|
||||
|
||||
_UPDATE() {
|
||||
echo -e "开始测速..."
|
||||
BESTIP=""
|
||||
BESTIP_IPV6="::"
|
||||
# 这里可以自己添加、修改 CFST 的运行参数
|
||||
./cfst -o "result_hosts.txt"
|
||||
# 需要测速 IPv6 请取消注释
|
||||
#./cfst -o "result_hosts_ipv6.txt" -f ipv6.txt
|
||||
|
||||
# 如果需要 "找不到满足条件的 IP 就一直循环测速下去",那么可以将下面的两个 exit 0 改为 _UPDATE 即可
|
||||
[[ ! -e "result_hosts.txt" ]] && echo "CFST 测速结果 IP 数量为 0,跳过下面步骤..." && exit 0
|
||||
|
||||
# 下面这行代码是 "找不到满足条件的 IP 就一直循环测速下去" 才需要的代码
|
||||
# 考虑到当指定了下载速度下限,但一个满足全部条件的 IP 都没找到时,CFST 就会输出所有 IP 结果
|
||||
# 因此当你指定 -sl 参数时,需要移除下面这段代码开头的 # 井号注释符,来做文件行数判断(比如下载测速数量:10 个,那么下面的值就设在为 11)
|
||||
#[[ $(cat result_hosts.txt|wc -l) > 11 ]] && echo "CFST 测速结果没有找到一个完全满足条件的 IP,重新测速..." && _UPDATE
|
||||
|
||||
BESTIP=$(sed -n "2,1p" result_hosts.txt | awk -F, '{print $1}')
|
||||
# 需要测速 IPv6 请取消注释
|
||||
#BESTIP_IPV6=$(sed -n "2,1p" result_hosts_ipv6.txt | awk -F, '{print $1}')
|
||||
|
||||
if [[ -z "${BESTIP}" ]]; then
|
||||
echo "CFST 测速结果 IP 数量为 0,跳过下面步骤..."
|
||||
exit 0
|
||||
fi
|
||||
echo ${BESTIP} > nowip_hosts.txt
|
||||
echo -e "最优 IPv4 IP 为 ${BESTIP}\n"
|
||||
# 需要测速 IPv6 请取消注释
|
||||
#echo -e "最优 IPv6 IP 为 ${BESTIP_IPV6}\n"
|
||||
|
||||
[[ -f cloudflare.conf ]] && rm cloudflare.conf
|
||||
|
||||
cat site.conf | while read domain
|
||||
do
|
||||
if [[ ${domain:0:1} != "#" && ${domain} != "" ]]; then
|
||||
echo "address=/${domain}/${BESTIP}" >> "cloudflare.conf"
|
||||
echo "address=/${domain}/${BESTIP_IPV6}" >> "cloudflare.conf"
|
||||
fi
|
||||
done
|
||||
|
||||
[[ -f /etc/dnsmasq.d/cloudflare.conf ]] && rm /etc/dnsmasq.d/cloudflare.conf
|
||||
cp cloudflare.conf /etc/dnsmasq.d/cloudflare.conf
|
||||
systemctl restart dnsmasq.service
|
||||
}
|
||||
|
||||
_UPDATE
|
||||
106
script/cfst_dnspod.sh
Normal file
106
script/cfst_dnspod.sh
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 项目: CloudflareSpeedTest 自动更新Dnspod优选解析
|
||||
# 版本: 1.0.0
|
||||
# 作者: imashen
|
||||
# --------------------------------------------------------------
|
||||
|
||||
# 清理历史残留
|
||||
rm -f result4.csv result6.csv
|
||||
# DNSPod API 凭据
|
||||
dnspod_token="${API_TOKEN}"
|
||||
dnspod_domain="${DOMAIN}"
|
||||
dnspod_record="${SUB_DOMAIN}"
|
||||
|
||||
# DNSPod API URL
|
||||
dnspod_api_url="https://dnsapi.cn"
|
||||
|
||||
# 获取记录 ID
|
||||
get_record_id() {
|
||||
local record_type=$1
|
||||
local response
|
||||
response=$(curl -s -X POST -d "login_token=$dnspod_token&format=json&domain=$dnspod_domain&record_type=$record_type" "$dnspod_api_url/Record.List")
|
||||
local record_id
|
||||
record_id=$(echo "$response" | jq -r --arg type "$record_type" '.records[] | select(.type == $type) | .id')
|
||||
echo "$record_id"
|
||||
}
|
||||
|
||||
# 创建 DNS 记录
|
||||
create_dns_record() {
|
||||
local record_type=$1
|
||||
local ip_address=$2
|
||||
local response
|
||||
response=$(curl -s -X POST -d "login_token=$dnspod_token&format=json&domain=$dnspod_domain&sub_domain=$dnspod_record&record_type=$record_type&record_line=默认&value=$ip_address" "$dnspod_api_url/Record.Create")
|
||||
local record_id
|
||||
record_id=$(echo "$response" | jq -r '.record.id')
|
||||
echo "$record_id"
|
||||
}
|
||||
|
||||
# 更新 DNS 记录
|
||||
update_dns_record() {
|
||||
local record_id=$1
|
||||
local record_type=$2
|
||||
local ip_address=$3
|
||||
curl -s -X POST -d "login_token=$dnspod_token&format=json&domain=$dnspod_domain&record_id=$record_id&sub_domain=$dnspod_record&record_type=$record_type&record_line=默认&value=$ip_address" "$dnspod_api_url/Record.Modify"
|
||||
}
|
||||
|
||||
# 运行 CFST v4
|
||||
./cfst -f ip.txt -n 500 -o result4.csv
|
||||
|
||||
# 读取 CSV 文件并提取优选 IPv4 地址
|
||||
preferred_ipv4=$(awk -F, 'NR==2 {print $1}' result4.csv)
|
||||
|
||||
# 检查是否获取到了 IPv4 地址
|
||||
if [ -z "$preferred_ipv4" ]; then
|
||||
echo "Failed to get the preferred IPv4 address."
|
||||
else
|
||||
echo "BETTER IPv4: $preferred_ipv4"
|
||||
|
||||
# 获取 IPv4 记录 ID
|
||||
ipv4_record_id=$(get_record_id "A")
|
||||
|
||||
if [ -n "$ipv4_record_id" ]; then
|
||||
# 更新 IPv4 记录
|
||||
update_dns_record "$ipv4_record_id" "A" "$preferred_ipv4"
|
||||
echo "Updated DNSPod record with IPv4: $preferred_ipv4"
|
||||
else
|
||||
# 创建 IPv4 记录
|
||||
new_ipv4_record_id=$(create_dns_record "A" "$preferred_ipv4")
|
||||
if [ -n "$new_ipv4_record_id" ]; then
|
||||
echo "Created DNSPod record with IPv4: $preferred_ipv4"
|
||||
else
|
||||
echo "Failed to create DNSPod record with IPv4."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 运行 CFST v6
|
||||
./cfst -f ipv6.txt -n 500 -o result6.csv
|
||||
|
||||
# 读取 CSV 文件并提取优选 IPv6 地址
|
||||
preferred_ipv6=$(awk -F, 'NR==2 {print $1}' result6.csv)
|
||||
|
||||
# 检查是否获取到了 IPv6 地址
|
||||
if [ -z "$preferred_ipv6" ]; then
|
||||
echo "Failed to get the preferred IPv6 address."
|
||||
else
|
||||
echo "BETTER IPv6: $preferred_ipv6"
|
||||
|
||||
# 获取 IPv6 记录 ID
|
||||
ipv6_record_id=$(get_record_id "AAAA")
|
||||
|
||||
if [ -n "$ipv6_record_id" ]; then
|
||||
# 更新 IPv6 记录
|
||||
update_dns_record "$ipv6_record_id" "AAAA" "$preferred_ipv6"
|
||||
echo "Updated DNSPod record with IPv6: $preferred_ipv6"
|
||||
else
|
||||
# 创建 IPv6 记录
|
||||
new_ipv6_record_id=$(create_dns_record "AAAA" "$preferred_ipv6")
|
||||
if [ -n "$new_ipv6_record_id" ]; then
|
||||
echo "Created DNSPod record with IPv6: $preferred_ipv6"
|
||||
else
|
||||
echo "Failed to create DNSPod record with IPv6."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
124
script/cfst_hosts.bat
Normal file
124
script/cfst_hosts.bat
Normal file
@@ -0,0 +1,124 @@
|
||||
:: --------------------------------------------------------------
|
||||
:: <09><>Ŀ: CloudflareSpeedTest <20>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD> Hosts
|
||||
:: <09>汾: 1.0.5
|
||||
:: <09><><EFBFBD><EFBFBD>: XIU2
|
||||
:: <09><>Ŀ: https://github.com/XIU2/CloudflareSpeedTest
|
||||
:: --------------------------------------------------------------
|
||||
@echo off
|
||||
Setlocal Enabledelayedexpansion
|
||||
|
||||
::<3A>ж<EFBFBD><D0B6>Ƿ<EFBFBD><C7B7>ѻ<EFBFBD><D1BB>ù<EFBFBD><C3B9><EFBFBD>ԱȨ<D4B1><C8A8>
|
||||
|
||||
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
|
||||
|
||||
if '%errorlevel%' NEQ '0' (
|
||||
goto UACPrompt
|
||||
) else ( goto gotAdmin )
|
||||
|
||||
::д<><D0B4> vbs <20>ű<EFBFBD><C5B1>Թ<EFBFBD><D4B9><EFBFBD>Ա<EFBFBD><D4B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><D0B1>ű<EFBFBD><C5B1><EFBFBD>bat<61><74>
|
||||
|
||||
:UACPrompt
|
||||
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
|
||||
echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"
|
||||
"%temp%\getadmin.vbs"
|
||||
exit /B
|
||||
|
||||
::<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ vbs <20>ű<EFBFBD><C5B1><EFBFBD><EFBFBD>ڣ<EFBFBD><DAA3><EFBFBD>ɾ<EFBFBD><C9BE>
|
||||
|
||||
:gotAdmin
|
||||
if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )
|
||||
pushd "%CD%"
|
||||
CD /D "%~dp0"
|
||||
|
||||
|
||||
::<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD><D0B6>Ƿ<EFBFBD><C7B7>Ի<EFBFBD><D4BB>ù<EFBFBD><C3B9><EFBFBD>ԱȨ<D4B1>ޣ<EFBFBD><DEA3><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>о<EFBFBD>ȥ<EFBFBD><C8A5>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>DZ<EFBFBD><C7B1>ű<EFBFBD><C5B1><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD>
|
||||
|
||||
|
||||
::<3A><><EFBFBD><EFBFBD> nowip_hosts.txt <20>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڣ<EFBFBD>˵<EFBFBD><CBB5><EFBFBD>ǵ<EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD>иýű<C3BD>
|
||||
if not exist "nowip_hosts.txt" (
|
||||
echo <20>ýű<C3BD><C5B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ CFST <20><><EFBFBD>ٺ<EFBFBD><D9BA><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD> IP <20><><EFBFBD>滻 Hosts <20>е<EFBFBD> Cloudflare CDN IP<49><50>
|
||||
echo ʹ<><CAB9>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD>Ķ<EFBFBD><C4B6><EFBFBD>https://github.com/XIU2/CloudflareSpeedTest/issues/42#issuecomment-768273768
|
||||
echo.
|
||||
echo <20><>һ<EFBFBD><D2BB>ʹ<EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD>Ƚ<EFBFBD> Hosts <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Cloudflare CDN IP ͳһ<CDB3><D2BB>Ϊһ<CEAA><D2BB> IP<49><50>
|
||||
set /p nowip="<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Cloudflare CDN IP <20><><EFBFBD>س<EFBFBD><D8B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>ò<EFBFBD><C3B2>裩:"
|
||||
echo !nowip!>nowip_hosts.txt
|
||||
echo.
|
||||
)
|
||||
|
||||
::<3A><> nowip_hosts.txt <20>ļ<EFBFBD><C4BC><EFBFBD>ȡ<EFBFBD><C8A1>ǰ Hosts <20><>ʹ<EFBFBD>õ<EFBFBD> Cloudflare CDN IP
|
||||
set /p nowip=<nowip_hosts.txt
|
||||
echo <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>...
|
||||
|
||||
|
||||
:: <20><><EFBFBD><EFBFBD> RESET <20>Ǹ<EFBFBD><C7B8><EFBFBD>Ҫ "<22>Ҳ<EFBFBD><D2B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><>һֱѭ<D6B1><D1AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȥ" <20><><EFBFBD>ܵ<EFBFBD><DCB5><EFBFBD><EFBFBD><D7BC><EFBFBD><EFBFBD>
|
||||
:: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܾͰ<DCBE><CDB0><EFBFBD><EFBFBD><EFBFBD> 3 <20><> goto :STOP <20><>Ϊ goto :RESET <20><><EFBFBD><EFBFBD>
|
||||
:RESET
|
||||
|
||||
|
||||
:: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD>ӡ<EFBFBD><D3A1><EFBFBD> CFST <20><><EFBFBD><EFBFBD><EFBFBD>в<EFBFBD><D0B2><EFBFBD><EFBFBD><EFBFBD>echo.| <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6>س<EFBFBD><D8B3>˳<EFBFBD><CBB3><EFBFBD><EFBFBD><EFBFBD><F2A3A8B2><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD> -p 0 <20><><EFBFBD><EFBFBD><EFBFBD>ˣ<EFBFBD>
|
||||
echo.|cfst.exe -o "result_hosts.txt"
|
||||
|
||||
|
||||
:: <20>жϽ<D0B6><CFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD>ڣ<EFBFBD><DAA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ 0
|
||||
if not exist result_hosts.txt (
|
||||
echo.
|
||||
echo CFST <20><><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD> IP <20><><EFBFBD><EFBFBD>Ϊ 0<><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>沽<EFBFBD><E6B2BD>...
|
||||
goto :STOP
|
||||
)
|
||||
|
||||
:: <20><>ȡ<EFBFBD><C8A1>һ<EFBFBD>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD> IP
|
||||
for /f "skip=1 tokens=1 delims=," %%i in ('more result_hosts.txt') do (
|
||||
SET bestip=%%i
|
||||
goto :END
|
||||
)
|
||||
|
||||
:END
|
||||
|
||||
:: <20>жϸոջ<D5B8>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20>Ƿ<EFBFBD>Ϊ<EFBFBD>գ<EFBFBD><D5A3>Լ<EFBFBD><D4BC>Ƿ<EFBFBD><C7B7>;<EFBFBD> IP һ<><D2BB>
|
||||
if "%bestip%"=="" (
|
||||
echo.
|
||||
echo CFST <20><><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD> IP <20><><EFBFBD><EFBFBD>Ϊ 0<><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>沽<EFBFBD><E6B2BD>...
|
||||
goto :STOP
|
||||
)
|
||||
if "%bestip%"=="%nowip%" (
|
||||
echo.
|
||||
echo CFST <20><><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD> IP <20><><EFBFBD><EFBFBD>Ϊ 0<><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>沽<EFBFBD><E6B2BD>...
|
||||
goto :STOP
|
||||
)
|
||||
|
||||
|
||||
:: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD><EFBFBD> "<22>Ҳ<EFBFBD><D2B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><>һֱѭ<D6B1><D1AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȥ" <20><><EFBFBD><EFBFBD>Ҫ<EFBFBD>Ĵ<EFBFBD><C4B4><EFBFBD>
|
||||
:: <20><><EFBFBD>ǵ<EFBFBD><C7B5><EFBFBD>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٶ<EFBFBD><D9B6><EFBFBD><EFBFBD>ޣ<EFBFBD><DEA3><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><>û<EFBFBD>ҵ<EFBFBD>ʱ<EFBFBD><CAB1>CFST <20>ͻ<EFBFBD><CDBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP <20><><EFBFBD><EFBFBD>
|
||||
:: <20><><EFBFBD>˵<EFBFBD><CBB5><EFBFBD>ָ<EFBFBD><D6B8> -sl <20><><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>Ҫ<EFBFBD>Ƴ<EFBFBD><C6B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ<EFBFBD><CEB4>뿪ͷ<EBBFAA><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD> :: ð<><C3B0>ע<EFBFBD>ͷ<EFBFBD><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>жϣ<D0B6><CFA3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>10 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ô<EFBFBD><C3B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ 11<31><31>
|
||||
::set /a v=0
|
||||
::for /f %%a in ('type result_hosts.txt') do set /a v+=1
|
||||
::if %v% GTR 11 (
|
||||
:: echo.
|
||||
:: echo CFST <20><><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD>û<EFBFBD><C3BB><EFBFBD>ҵ<EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IP<49><50><EFBFBD><EFBFBD><EFBFBD>²<EFBFBD><C2B2><EFBFBD>...
|
||||
:: goto :RESET
|
||||
::)
|
||||
|
||||
|
||||
echo %bestip%>nowip_hosts.txt
|
||||
echo.
|
||||
echo <20><> IP Ϊ %nowip%
|
||||
echo <20><> IP Ϊ %bestip%
|
||||
|
||||
CD /d "C:\Windows\System32\drivers\etc"
|
||||
echo.
|
||||
echo <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD> Hosts <20>ļ<EFBFBD><C4BC><EFBFBD>hosts_backup<75><70>...
|
||||
copy hosts hosts_backup
|
||||
echo.
|
||||
echo <20><>ʼ<EFBFBD>滻...
|
||||
(
|
||||
for /f "tokens=*" %%i in (hosts_backup) do (
|
||||
set s=%%i
|
||||
set s=!s:%nowip%=%bestip%!
|
||||
echo !s!
|
||||
)
|
||||
)>hosts
|
||||
|
||||
echo <20><><EFBFBD><EFBFBD>...
|
||||
echo.
|
||||
:STOP
|
||||
pause
|
||||
63
script/cfst_hosts.sh
Normal file
63
script/cfst_hosts.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
export PATH
|
||||
# --------------------------------------------------------------
|
||||
# 项目: CloudflareSpeedTest 自动更新 Hosts
|
||||
# 版本: 1.0.4
|
||||
# 作者: XIU2
|
||||
# 项目: https://github.com/XIU2/CloudflareSpeedTest
|
||||
# --------------------------------------------------------------
|
||||
|
||||
_CHECK() {
|
||||
while true
|
||||
do
|
||||
if [[ ! -e "nowip_hosts.txt" ]]; then
|
||||
echo -e "该脚本的作用为 CFST 测速后获取最快 IP 并替换 Hosts 中的 Cloudflare CDN IP。\n使用前请先阅读:https://github.com/XIU2/CloudflareSpeedTest/issues/42#issuecomment-768273848"
|
||||
echo -e "第一次使用,请先将 Hosts 中所有 Cloudflare CDN IP 统一改为一个 IP。"
|
||||
read -e -p "输入该 Cloudflare CDN IP 并回车(后续不再需要该步骤):" NOWIP
|
||||
if [[ ! -z "${NOWIP}" ]]; then
|
||||
echo ${NOWIP} > nowip_hosts.txt
|
||||
break
|
||||
else
|
||||
echo "该 IP 不能是空!"
|
||||
fi
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_UPDATE() {
|
||||
echo -e "开始测速..."
|
||||
NOWIP=$(head -1 nowip_hosts.txt)
|
||||
|
||||
# 这里可以自己添加、修改 CFST 的运行参数
|
||||
./cfst -o "result_hosts.txt"
|
||||
|
||||
# 如果需要 "找不到满足条件的 IP 就一直循环测速下去",那么可以将下面的两个 exit 0 改为 _UPDATE 即可
|
||||
[[ ! -e "result_hosts.txt" ]] && echo "CFST 测速结果 IP 数量为 0,跳过下面步骤..." && exit 0
|
||||
|
||||
# 下面这行代码是 "找不到满足条件的 IP 就一直循环测速下去" 才需要的代码
|
||||
# 考虑到当指定了下载速度下限,但一个满足全部条件的 IP 都没找到时,CFST 就会输出所有 IP 结果
|
||||
# 因此当你指定 -sl 参数时,需要移除下面这段代码开头的 # 井号注释符,来做文件行数判断(比如下载测速数量:10 个,那么下面的值就设在为 11)
|
||||
#[[ $(cat result_hosts.txt|wc -l) > 11 ]] && echo "CFST 测速结果没有找到一个完全满足条件的 IP,重新测速..." && _UPDATE
|
||||
|
||||
|
||||
BESTIP=$(sed -n "2,1p" result_hosts.txt | awk -F, '{print $1}')
|
||||
if [[ -z "${BESTIP}" ]]; then
|
||||
echo "CFST 测速结果 IP 数量为 0,跳过下面步骤..."
|
||||
exit 0
|
||||
fi
|
||||
echo ${BESTIP} > nowip_hosts.txt
|
||||
echo -e "\n旧 IP 为 ${NOWIP}\n新 IP 为 ${BESTIP}\n"
|
||||
|
||||
echo "开始备份 Hosts 文件(hosts_backup)..."
|
||||
\cp -f /etc/hosts /etc/hosts_backup
|
||||
|
||||
echo -e "开始替换..."
|
||||
sed -i 's/'${NOWIP}'/'${BESTIP}'/g' /etc/hosts
|
||||
echo -e "完成..."
|
||||
}
|
||||
|
||||
_CHECK
|
||||
_UPDATE
|
||||
63
script/cfst_hosts_mac.sh
Normal file
63
script/cfst_hosts_mac.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
export PATH
|
||||
# --------------------------------------------------------------
|
||||
# 项目: CloudflareSpeedTest 自动更新 Hosts
|
||||
# 版本: 1.0.4
|
||||
# 作者: XIU2
|
||||
# 项目: https://github.com/XIU2/CloudflareSpeedTest
|
||||
# --------------------------------------------------------------
|
||||
|
||||
_CHECK() {
|
||||
while true
|
||||
do
|
||||
if [[ ! -e "nowip_hosts.txt" ]]; then
|
||||
echo -e "该脚本的作用为 CFST 测速后获取最快 IP 并替换 Hosts 中的 Cloudflare CDN IP。\n使用前请先阅读:https://github.com/XIU2/CloudflareSpeedTest/issues/42#issuecomment-768273848"
|
||||
echo -e "第一次使用,请先将 Hosts 中所有 Cloudflare CDN IP 统一改为一个 IP。"
|
||||
read -e -p "输入该 Cloudflare CDN IP 并回车(后续不再需要该步骤):" NOWIP
|
||||
if [[ ! -z "${NOWIP}" ]]; then
|
||||
echo ${NOWIP} > nowip_hosts.txt
|
||||
break
|
||||
else
|
||||
echo "该 IP 不能是空!"
|
||||
fi
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_UPDATE() {
|
||||
echo -e "开始测速..."
|
||||
NOWIP=$(head -1 nowip_hosts.txt)
|
||||
|
||||
# 这里可以自己添加、修改 CFST 的运行参数
|
||||
./cfst -o "result_hosts.txt"
|
||||
|
||||
# 如果需要 "找不到满足条件的 IP 就一直循环测速下去",那么可以将下面的两个 exit 0 改为 _UPDATE 即可
|
||||
[[ ! -e "result_hosts.txt" ]] && echo "CFST 测速结果 IP 数量为 0,跳过下面步骤..." && exit 0
|
||||
|
||||
# 下面这行代码是 "找不到满足条件的 IP 就一直循环测速下去" 才需要的代码
|
||||
# 考虑到当指定了下载速度下限,但一个满足全部条件的 IP 都没找到时,CFST 就会输出所有 IP 结果
|
||||
# 因此当你指定 -sl 参数时,需要移除下面这段代码开头的 # 井号注释符,来做文件行数判断(比如下载测速数量:10 个,那么下面的值就设在为 11)
|
||||
#[[ $(cat result_hosts.txt|wc -l) > 11 ]] && echo "CFST 测速结果没有找到一个完全满足条件的 IP,重新测速..." && _UPDATE
|
||||
|
||||
|
||||
BESTIP=$(sed -n "2,1p" result_hosts.txt | awk -F, '{print $1}')
|
||||
if [[ -z "${BESTIP}" ]]; then
|
||||
echo "CFST 测速结果 IP 数量为 0,跳过下面步骤..."
|
||||
exit 0
|
||||
fi
|
||||
echo ${BESTIP} > nowip_hosts.txt
|
||||
echo -e "\n旧 IP 为 ${NOWIP}\n新 IP 为 ${BESTIP}\n"
|
||||
|
||||
echo "开始备份 Hosts 文件(hosts_backup)..."
|
||||
\cp -f /etc/hosts /etc/hosts_backup
|
||||
|
||||
echo -e "开始替换..."
|
||||
sed -i '' 's/'${NOWIP}'/'${BESTIP}'/g' /etc/hosts
|
||||
echo -e "完成..."
|
||||
}
|
||||
|
||||
_CHECK
|
||||
_UPDATE
|
||||
230
task/download.go
Normal file
230
task/download.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/XIU2/CloudflareSpeedTest/utils"
|
||||
|
||||
"github.com/VividCortex/ewma"
|
||||
)
|
||||
|
||||
const (
|
||||
bufferSize = 1024
|
||||
defaultURL = "https://cf.xiu2.xyz/url"
|
||||
defaultTimeout = 10 * time.Second
|
||||
defaultDisableDownload = false
|
||||
defaultTestNum = 10
|
||||
defaultMinSpeed float64 = 0.0
|
||||
)
|
||||
|
||||
var (
|
||||
URL = defaultURL
|
||||
Timeout = defaultTimeout
|
||||
Disable = defaultDisableDownload
|
||||
|
||||
TestCount = defaultTestNum
|
||||
MinSpeed = defaultMinSpeed
|
||||
)
|
||||
|
||||
func checkDownloadDefault() {
|
||||
if URL == "" {
|
||||
URL = defaultURL
|
||||
}
|
||||
if Timeout <= 0 {
|
||||
Timeout = defaultTimeout
|
||||
}
|
||||
if TestCount <= 0 {
|
||||
TestCount = defaultTestNum
|
||||
}
|
||||
if MinSpeed <= 0.0 {
|
||||
MinSpeed = defaultMinSpeed
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadSpeed(ipSet utils.PingDelaySet) (speedSet utils.DownloadSpeedSet) {
|
||||
checkDownloadDefault()
|
||||
if Disable {
|
||||
return utils.DownloadSpeedSet(ipSet)
|
||||
}
|
||||
if len(ipSet) <= 0 { // IP 数组长度(IP数量) 大于 0 时才会继续下载测速
|
||||
utils.Yellow.Println("[信息] 延迟测速结果 IP 数量为 0,跳过下载测速。")
|
||||
return
|
||||
}
|
||||
testNum := TestCount // 等待下载测速的队列数量 先默认等于 下载测速数量(-dn)
|
||||
if len(ipSet) < TestCount || MinSpeed > 0 { // 如果延迟测速并过滤后的 IP 数组长度(IP数量) 小于 下载测速数量(-dn),(即 -dn 预期数量是不够的),或者指定了 下载测速下限 (-sl) 条件(这就可能要全部下载测速一遍,直到找齐预期数量或测完为止),则 等待下载测速的队列数量 修正为 IP 数量
|
||||
testNum = len(ipSet)
|
||||
}
|
||||
if testNum < TestCount { // 如果 等待下载测速的队列数量 小于 下载测速数量(-dn),(显然 -dn 预期数量是不够的),所以 下载测速数量(-dn)修正为 等待下载测速的队列数量
|
||||
TestCount = testNum
|
||||
}
|
||||
|
||||
utils.Cyan.Printf("开始下载测速(下限:%.2f MB/s, 数量:%d, 队列:%d)\n", MinSpeed, TestCount, testNum)
|
||||
// 控制 下载测速进度条 与 延迟测速进度条 长度一致(强迫症)
|
||||
bar_a := len(strconv.Itoa(len(ipSet)))
|
||||
bar_b := " "
|
||||
for i := 0; i < bar_a; i++ {
|
||||
bar_b += " "
|
||||
}
|
||||
bar := utils.NewBar(TestCount, bar_b, "")
|
||||
for i := 0; i < testNum; i++ {
|
||||
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, "")
|
||||
speedSet = append(speedSet, ipSet[i]) // 高于下载速度下限时,添加到新数组中
|
||||
if len(speedSet) == TestCount { // 凑够满足条件的 IP 时(下载测速数量 -dn),就跳出循环
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
bar.Done()
|
||||
if MinSpeed == 0.00 { // 如果没有指定下载速度下限,则直接返回所有测速数据
|
||||
speedSet = utils.DownloadSpeedSet(ipSet)
|
||||
} else if utils.Debug && len(speedSet) == 0 { // 如果指定了下载速度下限,且是调试模式下,且没有找到任何一个满足条件的 IP 时,返回所有测速数据,供用户查看当前的测速结果,以便适当调低预期测速条件
|
||||
utils.Yellow.Println("[调试] 没有满足 下载速度下限 条件的 IP,忽略条件返回所有测速数据(方便下次测速时调整条件)。")
|
||||
speedSet = utils.DownloadSpeedSet(ipSet)
|
||||
}
|
||||
// 按速度排序
|
||||
sort.Sort(speedSet)
|
||||
return
|
||||
}
|
||||
|
||||
func getDialContext(ip *net.IPAddr) func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
var fakeSourceAddr string
|
||||
if isIPv4(ip.String()) {
|
||||
fakeSourceAddr = fmt.Sprintf("%s:%d", ip.String(), TCPPort)
|
||||
} else {
|
||||
fakeSourceAddr = fmt.Sprintf("[%s]:%d", ip.String(), TCPPort)
|
||||
}
|
||||
return func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, network, fakeSourceAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// 统一的请求报错调试输出
|
||||
func printDownloadDebugInfo(ip *net.IPAddr, err error, statusCode int, url, lastRedirectURL string, response *http.Response) {
|
||||
finalURL := url // 默认的最终 URL,这样当 response 为空时也能输出
|
||||
if lastRedirectURL != "" {
|
||||
finalURL = lastRedirectURL // 如果 lastRedirectURL 不是空,说明重定向过,优先输出最后一次要重定向至的目标
|
||||
} else if response != nil && response.Request != nil && response.Request.URL != nil {
|
||||
finalURL = response.Request.URL.String() // 如果 response 不为 nil,且 Request 和 URL 都不为 nil,则获取最后一次成功的响应地址
|
||||
}
|
||||
if url != finalURL { // 如果 URL 和最终地址不一致,说明有重定向,是该重定向后的地址引起的错误
|
||||
if statusCode > 0 { // 如果状态码大于 0,说明是后续 HTTP 状态码引起的错误
|
||||
utils.Red.Printf("[调试] IP: %s, 下载测速终止,HTTP 状态码: %d, 下载测速地址: %s, 出错的重定向后地址: %s\n", ip.String(), statusCode, url, finalURL)
|
||||
} else {
|
||||
utils.Red.Printf("[调试] IP: %s, 下载测速失败,错误信息: %v, 下载测速地址: %s, 出错的重定向后地址: %s\n", ip.String(), err, url, finalURL)
|
||||
}
|
||||
} else { // 如果 URL 和最终地址一致,说明没有重定向
|
||||
if statusCode > 0 { // 如果状态码大于 0,说明是后续 HTTP 状态码引起的错误
|
||||
utils.Red.Printf("[调试] IP: %s, 下载测速终止,HTTP 状态码: %d, 下载测速地址: %s\n", ip.String(), statusCode, url)
|
||||
} else {
|
||||
utils.Red.Printf("[调试] IP: %s, 下载测速失败,错误信息: %v, 下载测速地址: %s\n", ip.String(), err, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return download Speed
|
||||
func downloadHandler(ip *net.IPAddr) (float64, string) {
|
||||
var lastRedirectURL string // 用于记录最后一次重定向目标,以便在访问错误时输出
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{DialContext: getDialContext(ip)},
|
||||
Timeout: Timeout,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
lastRedirectURL = req.URL.String() // 记录每次重定向的目标,以便在访问错误时输出
|
||||
if len(via) > 10 { // 限制最多重定向 10 次
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
utils.Red.Printf("[调试] IP: %s, 下载测速地址重定向次数过多,终止测速,下载测速地址: %s\n", ip.String(), req.URL.String())
|
||||
}
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
if req.Header.Get("Referer") == defaultURL { // 当使用默认下载测速地址时,重定向不携带 Referer
|
||||
req.Header.Del("Referer")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequest("GET", URL, nil)
|
||||
if err != nil {
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
utils.Red.Printf("[调试] IP: %s, 下载测速请求创建失败,错误信息: %v, 下载测速地址: %s\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 {
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
printDownloadDebugInfo(ip, err, 0, URL, lastRedirectURL, response)
|
||||
}
|
||||
return 0.0, ""
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
printDownloadDebugInfo(ip, nil, response.StatusCode, URL, lastRedirectURL, response)
|
||||
}
|
||||
return 0.0, ""
|
||||
}
|
||||
|
||||
// 通过头部参数获取地区码
|
||||
colo := getHeaderColo(response.Header)
|
||||
|
||||
timeStart := time.Now() // 开始时间(当前)
|
||||
timeEnd := timeStart.Add(Timeout) // 加上下载测速时间得到的结束时间
|
||||
|
||||
contentLength := response.ContentLength // 文件大小
|
||||
buffer := make([]byte, bufferSize)
|
||||
|
||||
var (
|
||||
contentRead int64 = 0
|
||||
timeSlice = Timeout / 100
|
||||
timeCounter = 1
|
||||
lastContentRead int64 = 0
|
||||
)
|
||||
|
||||
var nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
|
||||
e := ewma.NewMovingAverage()
|
||||
|
||||
// 循环计算,如果文件下载完了(两者相等),则退出循环(终止测速)
|
||||
for contentLength != contentRead {
|
||||
currentTime := time.Now()
|
||||
if currentTime.After(nextTime) {
|
||||
timeCounter++
|
||||
nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
|
||||
e.Add(float64(contentRead - lastContentRead))
|
||||
lastContentRead = contentRead
|
||||
}
|
||||
// 如果超出下载测速时间,则退出循环(终止测速)
|
||||
if currentTime.After(timeEnd) {
|
||||
break
|
||||
}
|
||||
bufferRead, err := response.Body.Read(buffer)
|
||||
if err != nil {
|
||||
if err != io.EOF { // 如果文件下载过程中遇到报错(如 Timeout),且并不是因为文件下载完了,则退出循环(终止测速)
|
||||
break
|
||||
} else if contentLength == -1 { // 文件下载完成 且 文件大小未知,则退出循环(终止测速),例如:https://speed.cloudflare.com/__down?bytes=200000000 这样的,如果在 10 秒内就下载完成了,会导致测速结果明显偏低甚至显示为 0.00(下载速度太快时)
|
||||
break
|
||||
}
|
||||
// 获取上个时间片
|
||||
last_time_slice := timeStart.Add(timeSlice * time.Duration(timeCounter-1))
|
||||
// 下载数据量 / (用当前时间 - 上个时间片/ 时间片)
|
||||
e.Add(float64(contentRead-lastContentRead) / (float64(currentTime.Sub(last_time_slice)) / float64(timeSlice)))
|
||||
}
|
||||
contentRead += int64(bufferRead)
|
||||
}
|
||||
return e.Value() / (Timeout.Seconds() / 120), colo
|
||||
}
|
||||
208
task/httping.go
Normal file
208
task/httping.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
//"crypto/tls"
|
||||
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XIU2/CloudflareSpeedTest/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
Httping bool
|
||||
HttpingStatusCode int
|
||||
HttpingCFColo string
|
||||
HttpingCFColomap *sync.Map
|
||||
RegexpColoIATACode = regexp.MustCompile(`[A-Z]{3}`) // 匹配 IATA 机场地区码(俗称 机场三字码)的正则表达式
|
||||
RegexpColoCountryCode = regexp.MustCompile(`[A-Z]{2}`) // 匹配国家地区码的正则表达式(如 US、CN、UK 等)
|
||||
RegexpColoGcore = regexp.MustCompile(`^[a-z]{2}`) // 匹配城市地区码的正则表达式(小写,如 us、cn、uk 等)
|
||||
)
|
||||
|
||||
// pingReceived pingTotalTime
|
||||
func (p *Ping) httping(ip *net.IPAddr) (int, time.Duration, string) {
|
||||
hc := http.Client{
|
||||
Timeout: time.Second * 2,
|
||||
Transport: &http.Transport{
|
||||
DialContext: getDialContext(ip),
|
||||
//TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // 跳过证书验证
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse // 阻止重定向
|
||||
},
|
||||
}
|
||||
|
||||
// 先访问一次获得 HTTP 状态码 及 地区码
|
||||
var colo string
|
||||
{
|
||||
request, err := http.NewRequest(http.MethodHead, URL, nil)
|
||||
if err != nil {
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
utils.Red.Printf("[调试] IP: %s, 延迟测速请求创建失败,错误信息: %v, 测速地址: %s\n", ip.String(), err, URL)
|
||||
}
|
||||
return 0, 0, ""
|
||||
}
|
||||
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 {
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
utils.Red.Printf("[调试] IP: %s, 延迟测速失败,错误信息: %v, 测速地址: %s\n", ip.String(), err, URL)
|
||||
}
|
||||
return 0, 0, ""
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
//fmt.Println("IP:", ip, "StatusCode:", response.StatusCode, response.Request.URL)
|
||||
// 如果未指定的 HTTP 状态码,或指定的状态码不合规,则默认只认为 200、301、302 才算 HTTPing 通过
|
||||
if HttpingStatusCode == 0 || HttpingStatusCode < 100 && HttpingStatusCode > 599 {
|
||||
if response.StatusCode != 200 && response.StatusCode != 301 && response.StatusCode != 302 {
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
utils.Red.Printf("[调试] IP: %s, 延迟测速终止,HTTP 状态码: %d, 测速地址: %s\n", ip.String(), response.StatusCode, URL)
|
||||
}
|
||||
return 0, 0, ""
|
||||
}
|
||||
} else {
|
||||
if response.StatusCode != HttpingStatusCode {
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
utils.Red.Printf("[调试] IP: %s, 延迟测速终止,HTTP 状态码: %d, 指定的 HTTP 状态码 %d, 测速地址: %s\n", ip.String(), response.StatusCode, HttpingStatusCode, URL)
|
||||
}
|
||||
return 0, 0, ""
|
||||
}
|
||||
}
|
||||
|
||||
io.Copy(io.Discard, response.Body)
|
||||
|
||||
// 通过头部参数获取地区码
|
||||
colo = getHeaderColo(response.Header)
|
||||
|
||||
// 只有指定了地区才匹配机场地区码
|
||||
if HttpingCFColo != "" {
|
||||
// 判断是否匹配指定的地区码
|
||||
colo = p.filterColo(colo)
|
||||
if colo == "" { // 没有匹配到地区码或不符合指定地区则直接结束该 IP 测试
|
||||
if utils.Debug { // 调试模式下,输出更多信息
|
||||
utils.Red.Printf("[调试] IP: %s, 地区码不匹配: %s\n", ip.String(), colo)
|
||||
}
|
||||
return 0, 0, ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 循环测速计算延迟
|
||||
success := 0
|
||||
var delay time.Duration
|
||||
for i := 0; i < PingTimes; i++ {
|
||||
request, err := http.NewRequest(http.MethodHead, URL, nil)
|
||||
if err != nil {
|
||||
log.Fatal("意外的错误,情报告:", err)
|
||||
return 0, 0, ""
|
||||
}
|
||||
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 {
|
||||
request.Header.Set("Connection", "close")
|
||||
}
|
||||
startTime := time.Now()
|
||||
response, err := hc.Do(request)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
success++
|
||||
io.Copy(io.Discard, response.Body)
|
||||
_ = response.Body.Close()
|
||||
duration := time.Since(startTime)
|
||||
delay += duration
|
||||
}
|
||||
|
||||
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 {
|
||||
colomap.Store(colo, colo)
|
||||
}
|
||||
return colomap
|
||||
}
|
||||
|
||||
// 从响应头中获取 地区码 值
|
||||
func getHeaderColo(header http.Header) (colo string) {
|
||||
if header.Get("server") != "" {
|
||||
// 如果是 Cloudflare CDN
|
||||
// server: cloudflare
|
||||
// cf-ray: 7bd32409eda7b020-SJC
|
||||
if header.Get("server") == "cloudflare" {
|
||||
if colo = header.Get("cf-ray"); colo != "" {
|
||||
return RegexpColoIATACode.FindString(colo)
|
||||
}
|
||||
}
|
||||
// 如果是 CDN77 CDN(测试地址 https://www.cdn77.com
|
||||
// server: CDN77-Turbo
|
||||
// x-77-pop: losangelesUSCA // 美国的会显示为 USCA 不知道什么情况,暂时没做兼容,只提取 US
|
||||
// x-77-pop: frankfurtDE
|
||||
// x-77-pop: amsterdamNL
|
||||
// x-77-pop: singaporeSG
|
||||
if header.Get("server") == "CDN77-Turbo" {
|
||||
if colo = header.Get("x-77-pop"); colo != "" {
|
||||
return RegexpColoCountryCode.FindString(colo)
|
||||
}
|
||||
}
|
||||
// 如果是 Bunny CDN(测试地址 https://bunny.net
|
||||
// server: BunnyCDN-TW1-1121
|
||||
if colo = header.Get("server"); strings.Contains(colo, "BunnyCDN-") {
|
||||
return RegexpColoCountryCode.FindString(strings.TrimPrefix(colo, "BunnyCDN-")) // 去掉 BunnyCDN- 前缀再去匹配
|
||||
}
|
||||
}
|
||||
// 如果是 AWS CloudFront CDN(测试地址 https://d7uri8nf7uskq.cloudfront.net/tools/list-cloudfront-ips
|
||||
// x-amz-cf-pop: SIN52-P1
|
||||
if colo = header.Get("x-amz-cf-pop"); colo != "" {
|
||||
return RegexpColoIATACode.FindString(colo)
|
||||
}
|
||||
// 如果是 Fastly CDN(测试地址 https://fastly.jsdelivr.net/gh/XIU2/CloudflareSpeedTest@master/go.mod
|
||||
// x-served-by: cache-qpg1275-QPG
|
||||
// x-served-by: cache-fra-etou8220141-FRA, cache-hhr-khhr2060043-HHR(最后一个为实际位置)
|
||||
if colo = header.Get("x-served-by"); colo != "" {
|
||||
if matches := RegexpColoIATACode.FindAllString(colo, -1); len(matches) > 0 {
|
||||
return matches[len(matches)-1] // 因为 Fastly 的 x-served-by 可能包含多个地区码,所以只取最后一个
|
||||
}
|
||||
}
|
||||
// Gcore CDN 的头部信息(注意均为城市代码而非国家代码),测试地址 https://assets.gcore.pro/assets/icons/shield-lock.svg
|
||||
// x-id-fe: fr5-hw-edge-gc17
|
||||
// x-shard: fr5-shard0-default
|
||||
// x-id: fr5-hw-edge-gc28
|
||||
if colo = header.Get("x-id-fe"); colo != "" {
|
||||
if colo = RegexpColoGcore.FindString(colo); colo != "" {
|
||||
return strings.ToUpper(colo) // 将小写的地区码转换为大写
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有获取到头部信息,说明不是支持的 CDN,则直接返回空字符串
|
||||
return ""
|
||||
}
|
||||
|
||||
// 处理地区码
|
||||
func (p *Ping) filterColo(colo string) string {
|
||||
if colo == "" {
|
||||
return ""
|
||||
}
|
||||
// 如果没有指定 -cfcolo 参数,则直接返回
|
||||
if HttpingCFColomap == nil {
|
||||
return colo
|
||||
}
|
||||
// 匹配 机场地区码 是否为指定的地区
|
||||
_, ok := HttpingCFColomap.Load(colo)
|
||||
if ok {
|
||||
return colo
|
||||
}
|
||||
return ""
|
||||
}
|
||||
190
task/ip.go
Normal file
190
task/ip.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultInputFile = "ip.txt"
|
||||
|
||||
var (
|
||||
// TestAll test all ip
|
||||
TestAll = false
|
||||
// IPFile is the filename of IP Rangs
|
||||
IPFile = defaultInputFile
|
||||
IPText string
|
||||
)
|
||||
|
||||
func InitRandSeed() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func isIPv4(ip string) bool {
|
||||
return strings.Contains(ip, ".")
|
||||
}
|
||||
|
||||
func randIPEndWith(num byte) byte {
|
||||
if num == 0 { // 对于 /32 这种单独的 IP
|
||||
return byte(0)
|
||||
}
|
||||
return byte(rand.Intn(int(num)))
|
||||
}
|
||||
|
||||
type IPRanges struct {
|
||||
ips []*net.IPAddr
|
||||
mask string
|
||||
firstIP net.IP
|
||||
ipNet *net.IPNet
|
||||
}
|
||||
|
||||
func newIPRanges() *IPRanges {
|
||||
return &IPRanges{
|
||||
ips: make([]*net.IPAddr, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是单独 IP 则加上子网掩码,反之则获取子网掩码(r.mask)
|
||||
func (r *IPRanges) fixIP(ip string) string {
|
||||
// 如果不含有 '/' 则代表不是 IP 段,而是一个单独的 IP,因此需要加上 /32 /128 子网掩码
|
||||
if i := strings.IndexByte(ip, '/'); i < 0 {
|
||||
if isIPv4(ip) {
|
||||
r.mask = "/32"
|
||||
} else {
|
||||
r.mask = "/128"
|
||||
}
|
||||
ip += r.mask
|
||||
} else {
|
||||
r.mask = ip[i:]
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// 解析 IP 段,获得 IP、IP 范围、子网掩码
|
||||
func (r *IPRanges) parseCIDR(ip string) {
|
||||
var err error
|
||||
if r.firstIP, r.ipNet, err = net.ParseCIDR(r.fixIP(ip)); err != nil {
|
||||
log.Fatalln("ParseCIDR err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IPRanges) appendIPv4(d byte) {
|
||||
r.appendIP(net.IPv4(r.firstIP[12], r.firstIP[13], r.firstIP[14], d))
|
||||
}
|
||||
|
||||
func (r *IPRanges) appendIP(ip net.IP) {
|
||||
r.ips = append(r.ips, &net.IPAddr{IP: ip})
|
||||
}
|
||||
|
||||
// 返回第四段 ip 的最小值及可用数目
|
||||
func (r *IPRanges) getIPRange() (minIP, hosts byte) {
|
||||
minIP = r.firstIP[15] & r.ipNet.Mask[3] // IP 第四段最小值
|
||||
|
||||
// 根据子网掩码获取主机数量
|
||||
m := net.IPv4Mask(255, 255, 255, 255)
|
||||
for i, v := range r.ipNet.Mask {
|
||||
m[i] ^= v
|
||||
}
|
||||
total, _ := strconv.ParseInt(m.String(), 16, 32) // 总可用 IP 数
|
||||
if total > 255 { // 矫正 第四段 可用 IP 数
|
||||
hosts = 255
|
||||
return
|
||||
}
|
||||
hosts = byte(total)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *IPRanges) chooseIPv4() {
|
||||
if r.mask == "/32" { // 单个 IP 则无需随机,直接加入自身即可
|
||||
r.appendIP(r.firstIP)
|
||||
} else {
|
||||
minIP, hosts := r.getIPRange() // 返回第四段 IP 的最小值及可用数目
|
||||
for r.ipNet.Contains(r.firstIP) { // 只要该 IP 没有超出 IP 网段范围,就继续循环随机
|
||||
if TestAll { // 如果是测速全部 IP
|
||||
for i := 0; i <= int(hosts); i++ { // 遍历 IP 最后一段最小值到最大值
|
||||
r.appendIPv4(byte(i) + minIP)
|
||||
}
|
||||
} else { // 随机 IP 的最后一段 0.0.0.X
|
||||
r.appendIPv4(minIP + randIPEndWith(hosts))
|
||||
}
|
||||
r.firstIP[14]++ // 0.0.(X+1).X
|
||||
if r.firstIP[14] == 0 {
|
||||
r.firstIP[13]++ // 0.(X+1).X.X
|
||||
if r.firstIP[13] == 0 {
|
||||
r.firstIP[12]++ // (X+1).X.X.X
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IPRanges) chooseIPv6() {
|
||||
if r.mask == "/128" { // 单个 IP 则无需随机,直接加入自身即可
|
||||
r.appendIP(r.firstIP)
|
||||
} else {
|
||||
var tempIP uint8 // 临时变量,用于记录前一位的值
|
||||
for r.ipNet.Contains(r.firstIP) { // 只要该 IP 没有超出 IP 网段范围,就继续循环随机
|
||||
r.firstIP[15] = randIPEndWith(255) // 随机 IP 的最后一段
|
||||
r.firstIP[14] = randIPEndWith(255) // 随机 IP 的最后一段
|
||||
|
||||
targetIP := make([]byte, len(r.firstIP))
|
||||
copy(targetIP, r.firstIP)
|
||||
r.appendIP(targetIP) // 加入 IP 地址池
|
||||
|
||||
for i := 13; i >= 0; i-- { // 从倒数第三位开始往前随机
|
||||
tempIP = r.firstIP[i] // 保存前一位的值
|
||||
r.firstIP[i] += randIPEndWith(255) // 随机 0~255,加到当前位上
|
||||
if r.firstIP[i] >= tempIP { // 如果当前位的值大于等于前一位的值,说明随机成功了,可以退出该循环
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadIPRanges() []*net.IPAddr {
|
||||
ranges := newIPRanges()
|
||||
if IPText != "" { // 从参数中获取 IP 段数据
|
||||
IPs := strings.Split(IPText, ",") // 以逗号分隔为数组并循环遍历
|
||||
for _, IP := range IPs {
|
||||
IP = strings.TrimSpace(IP) // 去除首尾的空白字符(空格、制表符、换行符等)
|
||||
if IP == "" { // 跳过空的(即开头、结尾或连续多个 ,, 的情况)
|
||||
continue
|
||||
}
|
||||
ranges.parseCIDR(IP) // 解析 IP 段,获得 IP、IP 范围、子网掩码
|
||||
if isIPv4(IP) { // 生成要测速的所有 IPv4 / IPv6 地址(单个/随机/全部)
|
||||
ranges.chooseIPv4()
|
||||
} else {
|
||||
ranges.chooseIPv6()
|
||||
}
|
||||
}
|
||||
} else { // 从文件中获取 IP 段数据
|
||||
if IPFile == "" {
|
||||
IPFile = defaultInputFile
|
||||
}
|
||||
file, err := os.Open(IPFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() { // 循环遍历文件每一行
|
||||
line := strings.TrimSpace(scanner.Text()) // 去除首尾的空白字符(空格、制表符、换行符等)
|
||||
if line == "" { // 跳过空行
|
||||
continue
|
||||
}
|
||||
ranges.parseCIDR(line) // 解析 IP 段,获得 IP、IP 范围、子网掩码
|
||||
if isIPv4(line) { // 生成要测速的所有 IPv4 / IPv6 地址(单个/随机/全部)
|
||||
ranges.chooseIPv4()
|
||||
} else {
|
||||
ranges.chooseIPv6()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ranges.ips
|
||||
}
|
||||
149
task/tcping.go
Normal file
149
task/tcping.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XIU2/CloudflareSpeedTest/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
tcpConnectTimeout = time.Second * 1
|
||||
maxRoutine = 1000
|
||||
defaultRoutines = 200
|
||||
defaultPort = 443
|
||||
defaultPingTimes = 4
|
||||
)
|
||||
|
||||
var (
|
||||
Routines = defaultRoutines
|
||||
TCPPort int = defaultPort
|
||||
PingTimes int = defaultPingTimes
|
||||
)
|
||||
|
||||
type Ping struct {
|
||||
wg *sync.WaitGroup
|
||||
m *sync.Mutex
|
||||
ips []*net.IPAddr
|
||||
csv utils.PingDelaySet
|
||||
control chan bool
|
||||
bar *utils.Bar
|
||||
}
|
||||
|
||||
func checkPingDefault() {
|
||||
if Routines <= 0 {
|
||||
Routines = defaultRoutines
|
||||
}
|
||||
if TCPPort <= 0 || TCPPort >= 65535 {
|
||||
TCPPort = defaultPort
|
||||
}
|
||||
if PingTimes <= 0 {
|
||||
PingTimes = defaultPingTimes
|
||||
}
|
||||
}
|
||||
|
||||
func NewPing() *Ping {
|
||||
checkPingDefault()
|
||||
ips := loadIPRanges()
|
||||
return &Ping{
|
||||
wg: &sync.WaitGroup{},
|
||||
m: &sync.Mutex{},
|
||||
ips: ips,
|
||||
csv: make(utils.PingDelaySet, 0),
|
||||
control: make(chan bool, Routines),
|
||||
bar: utils.NewBar(len(ips), "可用:", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Ping) Run() utils.PingDelaySet {
|
||||
if len(p.ips) == 0 {
|
||||
return p.csv
|
||||
}
|
||||
if Httping {
|
||||
utils.Cyan.Printf("开始延迟测速(模式:HTTP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate)
|
||||
} else {
|
||||
utils.Cyan.Printf("开始延迟测速(模式:TCP, 端口:%d, 范围:%v ~ %v ms, 丢包:%.2f)\n", TCPPort, utils.InputMinDelay.Milliseconds(), utils.InputMaxDelay.Milliseconds(), utils.InputMaxLossRate)
|
||||
}
|
||||
for _, ip := range p.ips {
|
||||
p.wg.Add(1)
|
||||
p.control <- false
|
||||
go p.start(ip)
|
||||
}
|
||||
p.wg.Wait()
|
||||
p.bar.Done()
|
||||
sort.Sort(p.csv)
|
||||
return p.csv
|
||||
}
|
||||
|
||||
func (p *Ping) start(ip *net.IPAddr) {
|
||||
defer p.wg.Done()
|
||||
p.tcpingHandler(ip)
|
||||
<-p.control
|
||||
}
|
||||
|
||||
// bool connectionSucceed float32 time
|
||||
func (p *Ping) tcping(ip *net.IPAddr) (bool, time.Duration) {
|
||||
startTime := time.Now()
|
||||
var fullAddress string
|
||||
if isIPv4(ip.String()) {
|
||||
fullAddress = fmt.Sprintf("%s:%d", ip.String(), TCPPort)
|
||||
} else {
|
||||
fullAddress = fmt.Sprintf("[%s]:%d", ip.String(), TCPPort)
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", fullAddress, tcpConnectTimeout)
|
||||
if err != nil {
|
||||
return false, 0
|
||||
}
|
||||
defer conn.Close()
|
||||
duration := time.Since(startTime)
|
||||
return true, duration
|
||||
}
|
||||
|
||||
// pingReceived pingTotalTime
|
||||
func (p *Ping) checkConnection(ip *net.IPAddr) (recv int, totalDelay time.Duration, colo string) {
|
||||
if Httping {
|
||||
recv, totalDelay, colo = p.httping(ip)
|
||||
return
|
||||
}
|
||||
colo = "" // TCPing 不获取 colo
|
||||
for i := 0; i < PingTimes; i++ {
|
||||
if ok, delay := p.tcping(ip); ok {
|
||||
recv++
|
||||
totalDelay += delay
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Ping) appendIPData(data *utils.PingData) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
p.csv = append(p.csv, utils.CloudflareIPData{
|
||||
PingData: data,
|
||||
})
|
||||
}
|
||||
|
||||
// handle tcping
|
||||
func (p *Ping) tcpingHandler(ip *net.IPAddr) {
|
||||
recv, totalDlay, colo := p.checkConnection(ip)
|
||||
nowAble := len(p.csv)
|
||||
if recv != 0 {
|
||||
nowAble++
|
||||
}
|
||||
p.bar.Grow(1, strconv.Itoa(nowAble))
|
||||
if recv == 0 {
|
||||
return
|
||||
}
|
||||
data := &utils.PingData{
|
||||
IP: ip,
|
||||
Sended: PingTimes,
|
||||
Received: recv,
|
||||
Delay: totalDlay / time.Duration(recv),
|
||||
Colo: colo,
|
||||
}
|
||||
p.appendIPData(data)
|
||||
}
|
||||
172
tcping.go
172
tcping.go
@@ -1,172 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VividCortex/ewma"
|
||||
)
|
||||
|
||||
//bool connectionSucceed float32 time
|
||||
func tcping(ip net.IPAddr, tcpPort int) (bool, float32) {
|
||||
startTime := time.Now()
|
||||
var fullAddress string
|
||||
//fmt.Println(ip.String())
|
||||
if ipv6Mode { // IPv6 需要加上 []
|
||||
fullAddress = "[" + ip.String() + "]:" + strconv.Itoa(tcpPort)
|
||||
} else {
|
||||
fullAddress = ip.String() + ":" + strconv.Itoa(tcpPort)
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", fullAddress, tcpConnectTimeout)
|
||||
if err != nil {
|
||||
return false, 0
|
||||
} else {
|
||||
var endTime = time.Since(startTime)
|
||||
var duration = float32(endTime.Microseconds()) / 1000.0
|
||||
_ = conn.Close()
|
||||
return true, duration
|
||||
}
|
||||
}
|
||||
|
||||
//pingReceived pingTotalTime
|
||||
func checkConnection(ip net.IPAddr, tcpPort int) (int, float32) {
|
||||
pingRecv := 0
|
||||
var pingTime float32 = 0.0
|
||||
for i := 1; i <= failTime; i++ {
|
||||
pingSucceed, pingTimeCurrent := tcping(ip, tcpPort)
|
||||
if pingSucceed {
|
||||
pingRecv++
|
||||
pingTime += pingTimeCurrent
|
||||
}
|
||||
}
|
||||
return pingRecv, pingTime
|
||||
}
|
||||
|
||||
//return Success packetRecv averagePingTime specificIPAddr
|
||||
func tcpingHandler(ip net.IPAddr, tcpPort int, pingCount int, progressHandler func(e progressEvent)) (bool, int, float32, net.IPAddr) {
|
||||
ipCanConnect := false
|
||||
pingRecv := 0
|
||||
var pingTime float32 = 0.0
|
||||
for !ipCanConnect {
|
||||
pingRecvCurrent, pingTimeCurrent := checkConnection(ip, tcpPort)
|
||||
if pingRecvCurrent != 0 {
|
||||
ipCanConnect = true
|
||||
pingRecv = pingRecvCurrent
|
||||
pingTime = pingTimeCurrent
|
||||
} else {
|
||||
ip.IP[15]++
|
||||
if ip.IP[15] == 0 {
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if ipCanConnect {
|
||||
progressHandler(AvailableIPFound)
|
||||
for i := failTime; i < pingCount; i++ {
|
||||
pingSuccess, pingTimeCurrent := tcping(ip, tcpPort)
|
||||
progressHandler(NormalPing)
|
||||
if pingSuccess {
|
||||
pingRecv++
|
||||
pingTime += pingTimeCurrent
|
||||
}
|
||||
}
|
||||
return true, pingRecv, pingTime / float32(pingRecv), ip
|
||||
} else {
|
||||
progressHandler(NoAvailableIPFound)
|
||||
return false, 0, 0, net.IPAddr{}
|
||||
}
|
||||
}
|
||||
|
||||
func tcpingGoroutine(wg *sync.WaitGroup, mutex *sync.Mutex, ip net.IPAddr, tcpPort int, pingCount int, csv *[]CloudflareIPData, control chan bool, progressHandler func(e progressEvent)) {
|
||||
defer wg.Done()
|
||||
success, pingRecv, pingTimeAvg, currentIP := tcpingHandler(ip, tcpPort, pingCount, progressHandler)
|
||||
if success {
|
||||
mutex.Lock()
|
||||
var cfdata CloudflareIPData
|
||||
cfdata.ip = currentIP
|
||||
cfdata.pingReceived = pingRecv
|
||||
cfdata.pingTime = pingTimeAvg
|
||||
cfdata.pingCount = pingCount
|
||||
*csv = append(*csv, cfdata)
|
||||
mutex.Unlock()
|
||||
}
|
||||
<-control
|
||||
}
|
||||
|
||||
func GetDialContextByAddr(fakeSourceAddr string) func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
c, e := (&net.Dialer{}).DialContext(ctx, network, fakeSourceAddr)
|
||||
return c, e
|
||||
}
|
||||
}
|
||||
|
||||
//bool : can download,float32 downloadSpeed
|
||||
func DownloadSpeedHandler(ip net.IPAddr) (bool, float32) {
|
||||
var client = http.Client{
|
||||
Transport: nil,
|
||||
CheckRedirect: nil,
|
||||
Jar: nil,
|
||||
Timeout: downloadTestTime,
|
||||
}
|
||||
var fullAddress string
|
||||
if ipv6Mode { // IPv6 需要加上 []
|
||||
fullAddress = "[" + ip.String() + "]:443"
|
||||
} else {
|
||||
fullAddress = ip.String() + ":443"
|
||||
}
|
||||
client.Transport = &http.Transport{
|
||||
DialContext: GetDialContextByAddr(fullAddress),
|
||||
}
|
||||
response, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false, 0
|
||||
} else {
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
if response.StatusCode == 200 {
|
||||
timeStart := time.Now()
|
||||
timeEnd := timeStart.Add(downloadTestTime)
|
||||
|
||||
contentLength := response.ContentLength
|
||||
buffer := make([]byte, downloadBufferSize)
|
||||
|
||||
var contentRead int64 = 0
|
||||
var timeSlice = downloadTestTime / 100
|
||||
var timeCounter = 1
|
||||
var lastContentRead int64 = 0
|
||||
|
||||
var nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
|
||||
e := ewma.NewMovingAverage()
|
||||
|
||||
for contentLength != contentRead {
|
||||
var currentTime = time.Now()
|
||||
if currentTime.After(nextTime) {
|
||||
timeCounter += 1
|
||||
nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
|
||||
e.Add(float64(contentRead - lastContentRead))
|
||||
lastContentRead = contentRead
|
||||
}
|
||||
if currentTime.After(timeEnd) {
|
||||
break
|
||||
}
|
||||
bufferRead, err := response.Body.Read(buffer)
|
||||
contentRead += int64(bufferRead)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
break
|
||||
} else {
|
||||
e.Add(float64(contentRead-lastContentRead) / (float64(nextTime.Sub(currentTime)) / float64(timeSlice)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, float32(e.Value()) / (float32(downloadTestTime.Seconds()) / 120)
|
||||
} else {
|
||||
return false, 0
|
||||
}
|
||||
}
|
||||
}
|
||||
164
util.go
164
util.go
@@ -1,164 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cheggaaa/pb/v3"
|
||||
)
|
||||
|
||||
type CloudflareIPData struct {
|
||||
ip net.IPAddr
|
||||
pingCount int
|
||||
pingReceived int
|
||||
recvRate float32
|
||||
downloadSpeed float32
|
||||
pingTime float32
|
||||
}
|
||||
|
||||
func (cf *CloudflareIPData) getRecvRate() float32 {
|
||||
if cf.recvRate == 0 {
|
||||
pingLost := cf.pingCount - cf.pingReceived
|
||||
cf.recvRate = float32(pingLost) / float32(cf.pingCount)
|
||||
}
|
||||
return cf.recvRate
|
||||
}
|
||||
|
||||
func ExportCsv(filePath string, data []CloudflareIPData) {
|
||||
fp, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
log.Fatalf("创建文件["+filePath+"]句柄失败,%v", err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
w := csv.NewWriter(fp) //创建一个新的写入文件流
|
||||
w.Write([]string{"IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)"})
|
||||
w.WriteAll(convertToString(data))
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func (cf *CloudflareIPData) toString() []string {
|
||||
result := make([]string, 6)
|
||||
result[0] = cf.ip.String()
|
||||
result[1] = strconv.Itoa(cf.pingCount)
|
||||
result[2] = strconv.Itoa(cf.pingReceived)
|
||||
result[3] = strconv.FormatFloat(float64(cf.getRecvRate()), 'f', 2, 32)
|
||||
result[4] = strconv.FormatFloat(float64(cf.pingTime), 'f', 2, 32)
|
||||
result[5] = strconv.FormatFloat(float64(cf.downloadSpeed)/1024/1024, 'f', 2, 32)
|
||||
return result
|
||||
}
|
||||
|
||||
func convertToString(data []CloudflareIPData) [][]string {
|
||||
result := make([][]string, 0)
|
||||
for _, v := range data {
|
||||
result = append(result, v.toString())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var pingTime int
|
||||
var pingRoutine int
|
||||
|
||||
type progressEvent int
|
||||
|
||||
const (
|
||||
NoAvailableIPFound progressEvent = iota
|
||||
AvailableIPFound
|
||||
NormalPing
|
||||
)
|
||||
|
||||
var url string
|
||||
|
||||
var downloadTestTime time.Duration
|
||||
|
||||
const downloadBufferSize = 1024
|
||||
|
||||
var downloadTestCount int
|
||||
|
||||
//const defaultTcpPort = 443
|
||||
const tcpConnectTimeout = time.Second * 1
|
||||
|
||||
var failTime int
|
||||
|
||||
// 平均延迟排序(丢包另算)
|
||||
type CloudflareIPDataSet []CloudflareIPData
|
||||
|
||||
// 下载速度排序
|
||||
type CloudflareIPDataSetD []CloudflareIPData
|
||||
|
||||
func initRandSeed() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func randipEndWith(num int) uint8 {
|
||||
return uint8(rand.Intn(num))
|
||||
}
|
||||
|
||||
func GetRandomString() string {
|
||||
str := "0123456789abcdef"
|
||||
bytes := []byte(str)
|
||||
result := []byte{}
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < 4; i++ {
|
||||
result = append(result, bytes[r.Intn(len(bytes))])
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func ipPadding(ip string) string {
|
||||
var ipLength int
|
||||
var ipPrint string
|
||||
ipPrint = ip
|
||||
ipLength = len(ipPrint)
|
||||
if ipLength < 15 {
|
||||
for i := 0; i <= 15-ipLength; i++ {
|
||||
ipPrint += " "
|
||||
}
|
||||
}
|
||||
return ipPrint
|
||||
}
|
||||
|
||||
func handleProgressGenerator(pb *pb.ProgressBar) func(e progressEvent) {
|
||||
return func(e progressEvent) {
|
||||
switch e {
|
||||
case NoAvailableIPFound:
|
||||
pb.Add(pingTime)
|
||||
case AvailableIPFound:
|
||||
pb.Add(failTime)
|
||||
case NormalPing:
|
||||
pb.Increment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cfs CloudflareIPDataSet) Len() int {
|
||||
return len(cfs)
|
||||
}
|
||||
|
||||
func (cfs CloudflareIPDataSet) Less(i, j int) bool {
|
||||
if (cfs)[i].getRecvRate() != cfs[j].getRecvRate() {
|
||||
return cfs[i].getRecvRate() < cfs[j].getRecvRate()
|
||||
}
|
||||
return cfs[i].pingTime < cfs[j].pingTime
|
||||
}
|
||||
|
||||
func (cfs CloudflareIPDataSet) Swap(i, j int) {
|
||||
cfs[i], cfs[j] = cfs[j], cfs[i]
|
||||
}
|
||||
|
||||
func (cfs CloudflareIPDataSetD) Len() int {
|
||||
return len(cfs)
|
||||
}
|
||||
|
||||
func (cfs CloudflareIPDataSetD) Less(i, j int) bool {
|
||||
return cfs[i].downloadSpeed > cfs[j].downloadSpeed
|
||||
}
|
||||
|
||||
func (cfs CloudflareIPDataSetD) Swap(i, j int) {
|
||||
cfs[i], cfs[j] = cfs[j], cfs[i]
|
||||
}
|
||||
16
utils/color.go
Normal file
16
utils/color.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
// 由专业的库来处理多平台的颜色输出效果
|
||||
var (
|
||||
Red = color.New(color.FgRed) // 红色 31
|
||||
Green = color.New(color.FgGreen) // 绿色 32
|
||||
Yellow = color.New(color.FgYellow) // 黄色 33
|
||||
Blue = color.New(color.FgBlue, color.Bold) // 蓝色 34
|
||||
Magenta = color.New(color.FgMagenta) // 紫红色 35
|
||||
Cyan = color.New(color.FgHiCyan, color.Bold) // 青色 36
|
||||
White = color.New(color.FgWhite) // 白色 37
|
||||
)
|
||||
195
utils/csv.go
Normal file
195
utils/csv.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultOutput = "result.csv"
|
||||
maxDelay = 9999 * time.Millisecond
|
||||
minDelay = 0 * time.Millisecond
|
||||
maxLossRate float32 = 1.0
|
||||
)
|
||||
|
||||
var (
|
||||
InputMaxDelay = maxDelay
|
||||
InputMinDelay = minDelay
|
||||
InputMaxLossRate = maxLossRate
|
||||
Output = defaultOutput
|
||||
PrintNum = 10
|
||||
Debug = false // 是否开启调试模式
|
||||
)
|
||||
|
||||
// 是否打印测试结果
|
||||
func NoPrintResult() bool {
|
||||
return PrintNum == 0
|
||||
}
|
||||
|
||||
// 是否输出到文件
|
||||
func noOutput() bool {
|
||||
return Output == "" || Output == " "
|
||||
}
|
||||
|
||||
type PingData struct {
|
||||
IP *net.IPAddr
|
||||
Sended int
|
||||
Received int
|
||||
Delay time.Duration
|
||||
Colo string
|
||||
}
|
||||
|
||||
type CloudflareIPData struct {
|
||||
*PingData
|
||||
lossRate float32
|
||||
DownloadSpeed float64
|
||||
}
|
||||
|
||||
// 计算丢包率
|
||||
func (cf *CloudflareIPData) getLossRate() float32 {
|
||||
if cf.lossRate == 0 {
|
||||
pingLost := cf.Sended - cf.Received
|
||||
cf.lossRate = float32(pingLost) / float32(cf.Sended)
|
||||
}
|
||||
return cf.lossRate
|
||||
}
|
||||
|
||||
func (cf *CloudflareIPData) toString() []string {
|
||||
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
|
||||
}
|
||||
|
||||
func ExportCsv(data []CloudflareIPData) {
|
||||
if noOutput() || len(data) == 0 {
|
||||
return
|
||||
}
|
||||
fp, err := os.Create(Output)
|
||||
if err != nil {
|
||||
log.Fatalf("创建文件[%s]失败:%v", Output, err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
w := csv.NewWriter(fp) //创建一个新的写入文件流
|
||||
_ = w.Write([]string{"IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度(MB/s)", "地区码"})
|
||||
_ = w.WriteAll(convertToString(data))
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func convertToString(data []CloudflareIPData) [][]string {
|
||||
result := make([][]string, 0)
|
||||
for _, v := range data {
|
||||
result = append(result, v.toString())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 延迟丢包排序
|
||||
type PingDelaySet []CloudflareIPData
|
||||
|
||||
// 延迟条件过滤
|
||||
func (s PingDelaySet) FilterDelay() (data PingDelaySet) {
|
||||
if InputMaxDelay > maxDelay || InputMinDelay < minDelay { // 当输入的延迟条件不在默认范围内时,不进行过滤
|
||||
return s
|
||||
}
|
||||
if InputMaxDelay == maxDelay && InputMinDelay == minDelay { // 当输入的延迟条件为默认值时,不进行过滤
|
||||
return s
|
||||
}
|
||||
for _, v := range s {
|
||||
if v.Delay > InputMaxDelay { // 平均延迟上限,延迟大于条件最大值时,后面的数据都不满足条件,直接跳出循环
|
||||
break
|
||||
}
|
||||
if v.Delay < InputMinDelay { // 平均延迟下限,延迟小于条件最小值时,不满足条件,跳过
|
||||
continue
|
||||
}
|
||||
data = append(data, v) // 延迟满足条件时,添加到新数组中
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 丢包条件过滤
|
||||
func (s PingDelaySet) FilterLossRate() (data PingDelaySet) {
|
||||
if InputMaxLossRate >= maxLossRate { // 当输入的丢包条件为默认值时,不进行过滤
|
||||
return s
|
||||
}
|
||||
for _, v := range s {
|
||||
if v.getLossRate() > InputMaxLossRate { // 丢包几率上限
|
||||
break
|
||||
}
|
||||
data = append(data, v) // 丢包率满足条件时,添加到新数组中
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s PingDelaySet) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s PingDelaySet) Less(i, j int) bool {
|
||||
iRate, jRate := s[i].getLossRate(), s[j].getLossRate()
|
||||
if iRate != jRate {
|
||||
return iRate < jRate
|
||||
}
|
||||
return s[i].Delay < s[j].Delay
|
||||
}
|
||||
func (s PingDelaySet) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// 下载速度排序
|
||||
type DownloadSpeedSet []CloudflareIPData
|
||||
|
||||
func (s DownloadSpeedSet) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s DownloadSpeedSet) Less(i, j int) bool {
|
||||
return s[i].DownloadSpeed > s[j].DownloadSpeed
|
||||
}
|
||||
func (s DownloadSpeedSet) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s DownloadSpeedSet) Print() {
|
||||
if NoPrintResult() {
|
||||
return
|
||||
}
|
||||
if len(s) <= 0 { // IP数组长度(IP数量) 大于 0 时继续
|
||||
fmt.Println("\n[信息] 完整测速结果 IP 数量为 0,跳过输出结果。")
|
||||
return
|
||||
}
|
||||
dateString := convertToString(s) // 转为多维数组 [][]String
|
||||
if len(dateString) < PrintNum { // 如果IP数组长度(IP数量) 小于 打印次数,则次数改为IP数量
|
||||
PrintNum = len(dateString)
|
||||
}
|
||||
headFormat := "%-16s%-5s%-5s%-5s%-6s%-12s%-5s\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%-12s%-5s\n"
|
||||
dataFormat = "%-42s%-8s%-8s%-8s%-10s%-16s%-8s\n"
|
||||
break
|
||||
}
|
||||
}
|
||||
Cyan.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], dateString[i][6])
|
||||
}
|
||||
if !noOutput() {
|
||||
fmt.Printf("\n完整测速结果已写入 %v 文件,可使用记事本/表格软件查看。\n", Output)
|
||||
}
|
||||
}
|
||||
25
utils/progress.go
Normal file
25
utils/progress.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cheggaaa/pb/v3"
|
||||
)
|
||||
|
||||
type Bar struct {
|
||||
pb *pb.ProgressBar
|
||||
}
|
||||
|
||||
func NewBar(count int, MyStrStart, MyStrEnd string) *Bar {
|
||||
tmpl := fmt.Sprintf(`{{counters . }} {{ bar . "[" "-" (cycle . "↖" "↗" "↘" "↙" ) "_" "]"}} %s {{string . "MyStr" | green}} %s `, MyStrStart, MyStrEnd)
|
||||
bar := pb.ProgressBarTemplate(tmpl).Start(count)
|
||||
return &Bar{pb: bar}
|
||||
}
|
||||
|
||||
func (b *Bar) Grow(num int, MyStrVal string) {
|
||||
b.pb.Set("MyStr", MyStrVal).Add(num)
|
||||
}
|
||||
|
||||
func (b *Bar) Done() {
|
||||
b.pb.Finish()
|
||||
}
|
||||
Reference in New Issue
Block a user