-
Notifications
You must be signed in to change notification settings - Fork 10
/
main.go
1645 lines (1467 loc) · 52.2 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package main
import (
"bufio"
"context"
"encoding/csv"
"encoding/json"
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"os/exec"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/VividCortex/ewma"
"github.com/mattn/go-ieproxy"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/sinspired/CloudflareBestIP/task"
)
// 定义终端命令行变量
var (
DirectIP = flag.String("ip", "", "直接检测ip地址") // IP地址名称
File = flag.String("file", "txt.zip", "IP地址文件名称(*.txt或*.zip)") // IP地址文件名称
outFile = flag.String("outfile", "result.csv", "输出文件名称(自动设置)") // 输出文件名称
defaultPort = flag.Int("port", 443, "默认端口") // 端口
maxThreads = flag.Int("max", 200, "并发请求最大协程数") // 最大协程数
speedTestThreads = flag.Int("speedtest", 1, "下载测速协程数量,设为0禁用测速") // 下载测速协程数量
speedLimit = flag.Float64("speedlimit", 5, "最低下载速度(MB/s)") // 最低下载速度
speedTestURL = flag.String("url", "speed.cloudflare.com/__down?bytes=200000000", "测速文件地址") // 测速文件地址
enableTLS = flag.Bool("tls", true, "是否启用TLS") // TLS是否启用
multipleNum = flag.Float64("mulnum", 1, "多协程测速造成测速不准,可进行倍数补偿") // speedTest比较大时修改
tcpLimit = flag.Int("tcplimit", 2000, "TCP最大延迟(ms)") // TCP最大延迟(ms)
httpLimit = flag.Int("httplimit", 9999, "HTTP最大延迟(ms)") // HTTP最大延迟(ms)
countryCodes = flag.String("country", "", "国家代码(US,SG,JP,DE...),以逗号分隔,留空时检测所有") // 国家代码数组
ExcludedCounties = flag.String("not", "", "排除的国家代码(US,SG,JP,DE...)") // 排除的国家代码数组
DownloadipLib = flag.Bool("iplib", false, "为true时检查ip库中的文件并依次下载") // 自动下载一些知名的反代IP列表
Domain = flag.String("domain", "", "上传地址,默认为空,用Text2KV项目建立的简易文件存储storage.example.com")
Token = flag.String("token", "", "上传地址的token,默认为空")
RequestNum = flag.Int("num", 6, "测速结果数量") // 测速结果数量
DownloadTestAll = flag.Bool("dlall", false, "为true对有效满足延迟检测的有效ip测速,可能需要很长时间")
IPInfoApiKey = flag.String("api", "", "ip信息查询api,免费申请,api.ipapi.is,如留空则使用免费接口")
CheckGPT = flag.Bool("gpt", false, "控制是否要检测可用于chatgpt的PROYIP,须使用非CloudFlare平台的代理")
)
var ipLibs = map[string]string{
"txt.zip": "https://zip.baipiao.eu.org/",
"baipiaoge.zip": "https://zip.baipiao.eu.org/",
"ip_ProxyIPDB.txt": "https://ipdb.api.030101.xyz/?type=proxy",
"ip_CFv4IPDB.txt": "https://ipdb.api.030101.xyz/?type=cfv4",
"ip_BihaiCFIP.txt": "https://cfip.bihai.cf",
"ip_BihaiCloudCFIP.txt": "https://cloudcfip.bihai.cf",
}
const (
bufferSize = 1024
tcpTimeout = 2 * time.Second // 超时时间
httpTimeout = 4 * time.Second // 超时时间
maxDuration = 6 * time.Second // 最大延迟
dlTimeout = 10 * time.Second // 下载超时
)
var (
requestURL = "speed.cloudflare.com/cdn-cgi/trace" // 请求trace URL
locationsJsonUrl = "https://speed.cloudflare.com/locations" // location.json下载 URL
)
var (
startTime = time.Now() // 标记开始时间
countries []string // 国家代码数组
exceludedCountries []string // 排除的国家代码数组
locationMap map[string]location // IP位置数据
totalIPs int // IP总数
countProcessedInt32 int32 // 延迟检测已处理进度计数,使用原子计数
countAlive int // 延迟检测存活ip
percentage float64 // 检测进度百分比
totalResultChan []latencyTestResult // 存储延迟测速结果
countQualified int // 存储合格ip数量
baseLens int // 命令行输出基准长度
apiKey string
)
// 延迟检测结果结构体
type latencyTestResult struct {
ip string // IP地址
port int // 端口
tls bool // TLS状态
dataCenter string // 数据中心
region string // 地区
country string // 国家
city string // 城市
latency string // http延迟
// tcpDuration time.Duration // TCP请求延迟
tcpDuration string // tcp延迟
}
// 下载测速结果结构体
type speedTestResult struct {
latencyTestResult
downloadSpeed float64 // 下载速度
}
// 位置信息结构体
type location struct {
Iata string `json:"iata"`
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Cca2 string `json:"cca2"`
Region string `json:"region"`
City string `json:"city"`
}
// IPPort 结构体存储 IP 和端口信息
type IPPort struct {
IP string
Port int
}
var ipPortList []IPPort // 全局变量,存储 IP:port 格式的数据
// 尝试提升文件描述符的上限
func increaseMaxOpenFiles() {
if runtime.GOOS != "windows" {
fmt.Println("正在尝试提升文件描述符的上限...")
cmd := exec.Command("bash", "-c", "ulimit -n 10000")
_, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("提升文件描述符上限时出现错误: %v\n", err)
} else {
fmt.Printf("文件描述符上限已提升!\n")
}
} else {
fmt.Println("Windows系统不需要提升文件描述符上限")
}
}
func clearScreen() {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "cls")
case "linux", "darwin": // Linux and macOS
cmd = exec.Command("clear")
default:
fmt.Println("Unsupported platform")
return
}
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {
return
}
}
// 功能函数
// 检查location.json文件,如不存在,则从网络下载
func locationsJsonDownload() {
var locations []location // 创建location数组以存储json文件,
if _, err := os.Stat("locations.json"); os.IsNotExist(err) {
fmt.Println("正在从 " + locationsJsonUrl + " 下载 locations.json")
body, err := downloadWithIEProxy(locationsJsonUrl)
if err != nil {
fmt.Printf("下载失败: %v\n", err)
return
}
err = json.Unmarshal(body, &locations)
if err != nil {
fmt.Printf("无法解析JSON: %v\n", err)
return
}
file, err := os.Create("locations.json")
if err != nil {
fmt.Printf("无法创建文件: %v\n", err)
return
}
defer file.Close()
_, err = file.Write(body)
if err != nil {
fmt.Printf("无法写入文件: %v\n", err)
return
}
fmt.Println("\033[32m成功下载并创建 location.json\033[0m")
file, err = os.Open("locations.json")
if err != nil {
fmt.Printf("无法打开文件: %v\n", err)
return
}
defer file.Close()
body, err = io.ReadAll(file)
if err != nil {
fmt.Printf("无法读取文件: %v\n", err)
return
}
err = json.Unmarshal(body, &locations)
if err != nil {
fmt.Printf("无法解析JSON: %v\n", err)
return
}
} else {
fmt.Println("\033[0;90m本地 locations.json 已存在,无需重新下载\033[0m")
file, err := os.Open("locations.json")
if err != nil {
fmt.Printf("无法打开文件: %v\n", err)
return
}
defer file.Close()
body, err := io.ReadAll(file)
if err != nil {
fmt.Printf("无法读取文件: %v\n", err)
return
}
err = json.Unmarshal(body, &locations)
if err != nil {
fmt.Printf("无法解析JSON: %v\n", err)
return
}
}
// 读取位置数据并存入变量
locationMap = make(map[string]location)
for _, loc := range locations {
locationMap[loc.Iata] = loc
}
}
// downloadWithIEProxy 尝试使用IE代理设置下载文件
func downloadWithIEProxy(downloadURL string) ([]byte, error) {
proxyFunc := ieproxy.GetProxyFunc()
client := &http.Client{
Timeout: 15 * time.Second,
Transport: &http.Transport{Proxy: proxyFunc},
}
resp, err := client.Get(downloadURL)
if err != nil {
return nil, fmt.Errorf("下载时出错: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body) // 尝试读取响应体以获取更多错误信息
return nil, fmt.Errorf("非预期的HTTP状态码: %v, 响应体: %s", resp.Status, string(body))
}
return io.ReadAll(resp.Body)
}
// autoNetworkDetection 自动检测网络环境,返回一个bool值
func autoNetworkDetection() bool {
// 检查系统代理是否启用
if task.CheckProxyEnabled() {
fmt.Println("\033[2J\033[0;0H\033[31m检测到系统代理已启用,请关闭VPN后重试。\033[0m")
return false
} else {
fmt.Println("\033[90m系统代理未启用,检测tun模式代理……\033[0m")
// 检查Google.com是否可访问
if checkProxyUrl("https://www.google.com") {
fmt.Println("\033[31m已开启tun模式代理,可以访问外网,请关闭VPN后重试。\033[0m")
return false
} else {
fmt.Println("\033[90m未开启vpn,检测墙内网络是否正常……\033[0m")
}
}
// 检测Baidu是否可访问
if !checkNormalUrl("https://www.baidu.com") {
fmt.Println("\033[2J\033[0;0H\033[31m无互联网访问,请检查网络连接。\033[0m")
return false
} else {
// 清除输出内容
fmt.Print("\033[2J\033[0;0H")
fmt.Printf("\033[32m网络环境检测正常 \033[0m\n")
}
return true
}
// 代码转移到 task/windows.go task/linux.go 以支持跨系统编译
// // checkProxyEnabled 检测是否开启系统代理服务器
// func checkProxyEnabled() bool {
// if runtime.GOOS == "windows" {
// k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
// if err != nil {
// fmt.Println("无法打开注册表键:", err)
// return false
// }
// defer k.Close()
// proxyEnable, _, err := k.GetIntegerValue("ProxyEnable")
// if err != nil {
// fmt.Println("无法读取ProxyEnable值:", err)
// return false
// }
// return proxyEnable == 1
// }
// // 对于非Windows系统,我们可以检查环境变量
// return os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != ""
// }
// checkNormalUrl 尝试连接指定的URL,检查网络是否可访问
func checkNormalUrl(url string) bool {
resp, err := http.Get(url)
if err != nil {
// fmt.Printf("访问 %s 时未知错误:[ %v ]\n", url, err)
return false
}
defer resp.Body.Close()
// fmt.Println("检测可以ping通:" + url)
return true
}
// checkProxyUrl 根据域名检测连通性,自动检测代理服务器.
func checkProxyUrl(urlStr string) bool {
proxyFunc := ieproxy.GetProxyFunc()
client := &http.Client{
Timeout: 2 * time.Second,
Transport: &http.Transport{Proxy: proxyFunc},
}
resp, err := client.Get(urlStr)
if err != nil {
// fmt.Printf("连通性错误 %s: %v\n", urlStr, err)
return false
}
defer resp.Body.Close()
// fmt.Println("成功连接: " + urlStr)
return true
}
// 检查IP库是否存在并执行下载
func checkIPLib(url string, fileName string) {
fileInfo, err := os.Stat(fileName)
if os.IsNotExist(err) {
// 文件不存在,直接下载
fmt.Printf("\033[31mip库文件 %s 不存在,正在下载...\033[0m\n", fileName)
downloadFile(url, fileName)
} else {
// 文件存在,检查创建时间
fileModTime := fileInfo.ModTime()
if time.Since(fileModTime) > 12*time.Hour {
// 文件超过12小时,重新下载
fmt.Printf("\033[31mip库文件 %s 超过12小时,正在重新下载...\033[0m\n", fileName)
downloadFile(url, fileName)
} else {
// fmt.Printf("ip库文件 %s 存在且未超过12小时,无需下载\n",fileName)
}
}
}
// 文件下载函数
func downloadFile(url, fileName string) {
// 创建文件
out, err := os.Create(fileName)
if err != nil {
fmt.Println("无法创建文件:", err)
return
}
defer out.Close()
// 获取数据
resp, err := http.Get(url)
if err != nil {
fmt.Println("下载失败:", err)
return
}
defer resp.Body.Close()
// 写入文件
_, err = io.Copy(out, resp.Body)
if err != nil {
fmt.Println("写入文件失败:", err)
}
fmt.Println("下载状态...........................................[\033[32mok\033[0m]\n")
}
// 测速结果输出文件重命名函数
func resetOutFileName(File string) {
if strings.Contains(File, "_") {
FileName := strings.Split(File, ".")[0] // 去掉后缀名
resultName := strings.Split(FileName, "_")[1] // 分离名字字段
caser := cases.Title(language.English) // 使用English作为默认语言标签
resultName = caser.String(resultName) // 首字母大写
if *outFile == "result.csv" {
// 如果输出文件名为默认,即未指定
*outFile = "result_" + resultName + ".csv"
}
} else if File == "txt.zip" {
// 默认反代IP列表
if *outFile == "result.csv" {
*outFile = "result_Baipiaoge.csv"
}
} else if File == "ip.txt" {
// 默认ip列表
if *outFile == "result.csv" {
*outFile = "result_Test.csv"
}
} else {
FileName := strings.Split(File, ".")[0] // 去掉后缀名
caser := cases.Title(language.English) // 使用English作为默认语言标签
FileName = caser.String(FileName) // 首字母大写
if *outFile == "result.csv" {
*outFile = "result_" + FileName + ".csv"
}
}
}
// getUniqueIPs 处理 IP 列表并去重,返回唯一 IP 的切片。
func getUniqueIPs(content []byte) []string {
// 将内容按行分割成 IP 列表
ips := strings.Split(string(content), "\n")
ipSet := make(map[string]struct{})
// 遍历 IP 列表,去重
for _, ip := range ips {
if ip != "" {
ipSet[ip] = struct{}{}
}
}
// 将集合转换回切片
uniqueIPs := make([]string, 0, len(ipSet))
for ip := range ipSet {
uniqueIPs = append(uniqueIPs, ip)
}
return uniqueIPs
}
// pause 暂停程序,等待用户按任意键继续。
func pause() {
fmt.Println("按任意键继续...")
fmt.Scanln()
}
// processASNZipedFiles 处理文件格式为“ASN-Tls-Port.txt”格式的压缩包文件。
// 它计算所有唯一 IP 的总数,并检测有效的 IP。
func processASNZipedFiles(fileInfos []task.FileInfo) {
totalAliveIPs := 0
totalIPs = 0
// 遍历每个文件信息,处理 IP 列表
for _, info := range fileInfos {
// 获取唯一的 IP 列表
uniqueIPs := getUniqueIPs(info.Content)
totalIPs += len(uniqueIPs)
}
fmt.Println("\n成功获取去重ip列表,开始TCP/HTTP延迟检测...\n")
// 检测每个文件中的有效 IP
for _, info := range fileInfos {
// 获取唯一的 IP 列表
uniqueIPs := getUniqueIPs(info.Content)
// 延迟检测 IP 是否有效
aliveCount := delayedDetectionIPs(uniqueIPs, info.TLS, info.Port)
totalAliveIPs += aliveCount
}
// 如果没有发现有效的 IP,输出提示并退出程序
if len(totalResultChan) == 0 {
fmt.Println("\033[31m没有发现有效的 IP\033[0m")
os.Exit(0)
}
}
// 处理单个ip列表,txt格式
func processIPListFile(fileName string) {
totalAliveIPs := 0
ips, err := readIPs(fileName)
if err != nil {
fmt.Printf("读取 IP 时出错: %v\n", err)
return
}
totalIPs = len(ips) + len(ipPortList) // 总ip数,包括单行ip和带端口ip数
if totalIPs == 0 {
// 未读取ip退出程序
fmt.Println("\033[31m未读取到IP数据\033[0m ")
os.Exit(0)
} else if len(ipPortList) == 0 {
// 如果带端口ip数为0,则直接检测单行ip数组
aliveCount := delayedDetectionIPs(ips, *enableTLS, *defaultPort)
totalAliveIPs += aliveCount
} else {
for _, ip := range ips {
// 混杂的情况,把单行ip加上默认端口添加到ipPortList数组中统一处理
ipPortList = append(ipPortList, IPPort{ip, *defaultPort})
}
var ipsPortListStr []string // delayedDetectionIPs接受的结构
for _, ipPort := range ipPortList {
// 将 ipPortList 转换为 []string,{ip:port}格式,并逐条追加到 ipsPortListStr 列表中
ipsPortListStr = append(ipsPortListStr, fmt.Sprintf("%s:%d", ipPort.IP, ipPort.Port))
}
// ipsPortListStr 已包含了端口,所以就不需要传入端口
aliveCount := delayedDetectionIPs(ipsPortListStr, *enableTLS, 0)
totalAliveIPs += aliveCount
}
if len(totalResultChan) == 0 {
fmt.Println("\033[31m没有发现有效的IP\033[0m ")
os.Exit(0)
}
}
// logWithProgress 显示进度条
func logWithProgress(countProcessed, total int) {
if total != 0 {
percentage := float64(countProcessed) / float64(total) * 100
// barLength := 29 // 进度条长度
// baseLens = len("-IP 64.110.104.30 端口 443 位置:JP KIX 延迟 158 ms [ 489 ms ]")
baseLens = len(fmt.Sprintf("-IP %-15s 端口 %-5d 位置:%2s %3s 延迟 %3d ms [ %-3d ms ]\n", "255.255.255.255", 65001, "US", "SJX", 999, 999))
totalCountLen := len(strconv.Itoa(totalIPs))
alivelen := (totalCountLen - 1)
if (totalCountLen-2)*2+1 >= (totalCountLen - 1) {
alivelen = (totalCountLen - 2) * 2
}
barLength := baseLens - len("进度") - len("100.00% / 存活 IP: /") - totalCountLen*2 - alivelen - 3
progress := int(percentage / 100 * float64(barLength))
// 构建进度条
bar := fmt.Sprintf("\033[32m%s\033[0m%s", strings.Repeat("■", progress), strings.Repeat("▣", barLength-progress))
// 并发检测进度显示
fmt.Printf("\r进度%s\033[1;32m%6.2f%%\033[0m \033[90m%d\033[0m/\033[90m%d \033[0m", bar, percentage, countProcessed, total)
}
}
// delayedDetectionIPs 对给定的 IP 列表进行延迟检测,并返回存活 IP 的数量。
// 参数:
// - ips: 待检测的 IP 列表。
// - enableTLS: 是否启用 TLS。
// - port: 端口号。
func delayedDetectionIPs(ips []string, enableTLS bool, port int) int {
// 获取文件名中是否有 TLS 信息
fileName := strings.Split(*File, ".")[0]
if strings.Contains(fileName, "-") {
TlsStatus := strings.Split(fileName, "-")[1]
if TlsStatus == "0" {
enableTLS = false
}
}
var wg sync.WaitGroup
wg.Add(len(ips))
// 创建一个长度为输入数据长度的通道
resultChan := make(chan latencyTestResult, len(ips))
thread := make(chan struct{}, *maxThreads)
total := totalIPs // IP 数据总数
for _, ip := range ips {
thread <- struct{}{}
go func(ip string) {
defer func() {
<-thread
wg.Done()
// countProcessed++ // 已处理 IP 数计数器
atomic.AddInt32(&countProcessedInt32, 1) // 使用原子操作增加计数器
countProcessedInt := int(atomic.LoadInt32(&countProcessedInt32))
// 调用logWithProgress 函数输出进度信息
logWithProgress(countProcessedInt, total)
fmt.Printf("存活 IP: \033[1;32;5m%d\033[0m\r", countAlive)
}()
// 如果 IP 格式为 ip:port,则分离 IP 和端口
if strings.Contains(ip, ":") && strings.Count(ip, ":") == 1 {
ipPort := strings.Split(ip, ":")
if len(ipPort) == 2 {
ipAddr := ipPort[0]
portStr := ipPort[1]
// 验证 IP 地址格式
if net.ParseIP(ipAddr) == nil {
fmt.Printf("无效 IP 地址: %s\n", ipAddr)
return
}
// 验证端口格式
if portStr == "" {
ip = ipAddr
port = *defaultPort // 使用默认端口
} else {
portInt, err := strconv.Atoi(portStr)
if err != nil || portInt < 1 || portInt > 65535 {
fmt.Printf("无效端口: %s\n", portStr)
return
}
// 分离并验证成功,重新赋值
ip = ipAddr
port = portInt
}
}
}
// 检测平均延迟和丢包率
var (
recv int
totalDelay time.Duration
PingTimes = 4
)
for i := 0; i < PingTimes; i++ {
if ok, delay := tcping(ip, port); ok {
recv++
totalDelay += delay
}
}
if recv == 0 {
return
}
Sended := PingTimes
Received := recv
LossRate := float64((Sended - Received) / Sended)
if LossRate >= 0.5 {
// fmt.Printf("丢包率错误:%.1f\n", LossRate)
return
}
tcpDuration := totalDelay / time.Duration(Received)
// httping 延迟检测
start := time.Now()
client := http.Client{
Transport: &http.Transport{
// 使用 DialContext 函数
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return (&net.Dialer{
// Timeout: httpTimeout,
// KeepAlive: 0,
}).DialContext(ctx, network, net.JoinHostPort(ip, strconv.Itoa(port)))
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 阻止重定向
},
Timeout: httpTimeout,
}
var protocol string
if enableTLS {
protocol = "https://"
} else {
protocol = "http://"
}
requestURL := protocol + requestURL
req, _ := http.NewRequest(http.MethodGet, requestURL, nil)
// 添加用户代理
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
req.Close = true
resp, err := client.Do(req)
if err != nil {
// fmt.Println("http错误:", err)
return
}
if resp.StatusCode != http.StatusOK {
// fmt.Printf("ip:%s,状态码:%d \n", ip, resp.StatusCode)
return
}
duration := time.Since(start)
if duration > maxDuration {
// fmt.Printf("http延时:%v,最大允许:%v \033[31m超时\033[0m] \n", duration, maxDuration)
return
}
body, err := io.ReadAll(resp.Body)
// var colo string
// if resp.Header.Get("Server") == "cloudflare" {
// str := resp.Header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC
// colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str)
// } else {
// str := resp.Header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1
// colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str)
// }
// pause()
if strings.Contains(string(body), "uag=Mozilla/5.0") {
if matches := regexp.MustCompile(`colo=([A-Z]{3})`).FindStringSubmatch(string(body)); len(matches) > 1 {
dataCenter := matches[1]
loc, ok := locationMap[dataCenter]
// 排除的国家代码
if len(exceludedCountries) != 0 && containsIgnoreCase(exceludedCountries, loc.Cca2) {
return
}
// 根据 TCP 和 HTTP 延迟筛选检测结果
if float64(tcpDuration.Milliseconds()) <= float64(*tcpLimit) && float64(duration.Milliseconds()) <= float64(*httpLimit) {
// 根据国家代码筛选检测结果,如果为空,则不筛选
if len(countries) == 0 || containsIgnoreCase(countries, loc.Cca2) {
countAlive++ // 记录存活 IP 数量
if ok {
fmt.Printf("-IP %-15s 端口 %-5d 位置:%2s %3s 延迟 %3d ms [ \033[90m%-3d ms\033[0m ]\n", ip, port, loc.Cca2, dataCenter, tcpDuration.Milliseconds(), duration.Milliseconds())
resultChan <- latencyTestResult{ip, port, enableTLS, dataCenter, loc.Region, loc.Cca2, loc.City, fmt.Sprintf("%d", duration.Milliseconds()), fmt.Sprintf("%d", tcpDuration.Milliseconds())}
} else {
fmt.Printf("-IP %-15s 端口 %-5d 位置信息未知 延迟 %-3d ms \n", ip, port, tcpDuration.Milliseconds())
resultChan <- latencyTestResult{ip, port, enableTLS, dataCenter, "", "", "", fmt.Sprintf("%d", duration.Milliseconds()), fmt.Sprintf("%d", tcpDuration.Milliseconds())}
}
}
} else {
// fmt.Printf("-IP %-15s 端口 %-5d 位置:%2s%3s tcp延迟 %3d ms http延迟 %3d [不合格] \n", ip, port, loc.Cca2, dataCenter, loc.City, tcpDuration.Milliseconds(), duration.Milliseconds())
}
}
} else {
return
}
}(ip)
}
wg.Wait()
close(resultChan)
// 把通道里的内容添加到全局变量 totalResultChan 数组中,以便统一处理,增加效率
for res := range resultChan {
totalResultChan = append(totalResultChan, res)
}
// 并发检测执行完毕后输出信息
if int(atomic.LoadInt32(&countProcessedInt32)) == total {
time.Sleep(200 * time.Millisecond)
countProcessedInt := int(atomic.LoadInt32(&countProcessedInt32))
logWithProgress(countProcessedInt, total)
fmt.Printf("存活 IP: \033[1;32;5m%-3d\033[0m \r", countAlive)
fmt.Printf("\n\nTCP/HTTP 延迟检测完成!\n")
// pause()
}
return countAlive
}
// 从文件中读取IP地址并处理
func readIPs(File string) ([]string, error) {
file, err := os.Open(File)
if err != nil {
return nil, err
}
defer file.Close()
// 创建一个 map 存储不重复的 IP 地址
ipMap := make(map[string]struct{})
scanner := bufio.NewScanner(file)
for scanner.Scan() {
ipAddr := scanner.Text()
if len(ipAddr) < 7 {
continue
}
// 如果是碧海的ip库,把ip和端口之间的制表符替换为":"以便后续代码处理
if strings.Count(ipAddr, "\t") == 1 {
ipAddr = strings.Replace(ipAddr, "\t", ":", 1)
}
// 判断是否为 CIDR 格式的 IP 地址
if strings.Contains(ipAddr, "/") && strings.Count(ipAddr, ":") != 1 && strings.Count(ipAddr, "#") != 1 {
ip, ipNet, err := net.ParseCIDR(ipAddr)
if err != nil {
fmt.Printf("无法解析CIDR格式的IP: %v\n", err)
continue
}
for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); inc(ip) {
ipMap[ip.String()] = struct{}{}
}
} else if strings.Contains(ipAddr, ":") || strings.Contains(ipAddr, "#") {
if strings.Count(ipAddr, ":") > 1 {
// IPv6 地址
ipMap[ipAddr] = struct{}{}
} else if strings.Count(ipAddr, ":") == 1 {
// 带端口的IP列表,以:分割ip与port,IP:port 格式
if strings.Contains(ipAddr, "#") {
ipPort := strings.Split(ipAddr, "#")[0]
ip := strings.Split(ipPort, ":")[0]
portStr := strings.Split(ipPort, ":")[1]
portStr = strings.TrimSpace(portStr)
port, err := strconv.Atoi(portStr)
if err != nil {
fmt.Printf("%s端口转换错误:%v\n", ipAddr, err)
continue
}
// ipMap[ip] = struct{}{}
ipPortList = append(ipPortList, IPPort{IP: ip, Port: port}) // 存储 IP:port 格式的数据
} else {
ip := strings.Split(ipAddr, ":")[0]
portStr := strings.Split(ipAddr, ":")[1]
port, err := strconv.Atoi(portStr)
if err != nil {
fmt.Println(ipAddr)
fmt.Printf("%s端口转换错误:%v\n", ipAddr, err)
continue
}
// ipMap[ip] = struct{}{}
ipPortList = append(ipPortList, IPPort{IP: ip, Port: port}) // 存储 IP:port 格式的数据
}
}
} else {
ipMap[ipAddr] = struct{}{}
}
}
// 将 map 的键转换回切片,获得去重的ip地址
ips := make([]string, 0, len(ipMap))
for ip := range ipMap {
ips = append(ips, ip)
}
fmt.Println("\n成功获取去重ip列表,开始TCP/HTTP延迟检测...\n")
return ips, scanner.Err()
}
// inc函数实现ip地址自增
func inc(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
// 检测反代ip是否被chatgpt拉黑
func checkGptProxy() {
println("检测反代ip是否可用于chatgpt")
var wg sync.WaitGroup
var count int32
var gptProxyAlive int
port := "443"
// 创建一个长度为输入数据长度的通道
var resultGptAlive []string
thread := make(chan struct{}, *maxThreads)
total := len(totalResultChan)
wg.Add(len(totalResultChan))
for _, res := range totalResultChan {
thread <- struct{}{}
go func(res latencyTestResult) {
defer func() {
<-thread
wg.Done()
// countProcessed++ // 已处理 IP 数计数器
atomic.AddInt32(&count, 1) // 使用原子操作增加计数器
countProcessedInt := int(atomic.LoadInt32(&count))
// 调用logWithProgress 函数输出进度信息
logWithProgress(countProcessedInt, total)
fmt.Printf("存活 IP: \033[1;32;5m%d\033[0m\r", gptProxyAlive)
}()
gptAlive := task.CheckGptProxy(res.ip, port)
if gptAlive {
resultGptAlive = append(resultGptAlive, res.ip)
gptProxyAlive++
println("找到一个哦!")
} else {
println("不合格")
}
}(res)
}
wg.Wait()
if len(resultGptAlive) > 0 {
for _, ip := range resultGptAlive {
println("可以访问chatgpt的ip:%s", ip)
}
} else {
println("没有可以访问chatgpt的proxyip")
}
pause()
}
// 下载测速函数
func getDownloadSpeed(ip string, port int, enableTLS bool, latency string, tcplatency string, country string) float64 {
var protocol string
if enableTLS {
protocol = "https://"
} else {
protocol = "http://"
}
fullURL := protocol + *speedTestURL
// Create request
req, err := http.NewRequest("GET", fullURL, nil)
if err != nil {
fmt.Printf("-IP %-15s 端口 %-5s 国家 %s \033[9;31m测速无效1\033[0m%s\n", ip, strconv.Itoa(port), country, strings.Repeat(" ", 18))
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")
// Mark start time
// startTime := time.Now()
// Create HTTP client
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return (&net.Dialer{
// Timeout: httpTimeout,
KeepAlive: dlTimeout,
}).DialContext(ctx, network, net.JoinHostPort(ip, strconv.Itoa(port)))
},
},
Timeout: dlTimeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) > 10 {
return http.ErrUseLastResponse
}
return nil
},
}
// timeStart := time.Now() // 开始时间(当前)
// Send request
response, err := client.Do(req)
if err != nil {
fmt.Printf("-IP %-15s 端口 %-5s 国家 %s \033[9;31m测速无效\033[0m%s\n", ip, strconv.Itoa(port), country, strings.Repeat(" ", 18))
return 0.0
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return 0.0
}
timeStart := time.Now() // 开始时间(当前)
timeEnd := timeStart.Add(dlTimeout) // 加上下载测速时间得到的结束时间
contentLength := response.ContentLength // 文件大小
buffer := make([]byte, bufferSize)
var (
contentRead int64 = 0
timeSlice = dlTimeout / 100
timeCounter = 1
lastContentRead int64 = 0
)
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)
}
speed := e.Value() / (dlTimeout.Seconds() / 120) / (1024 * 1024)
// 输出结果
// fmt.Printf("-IP %-15s 端口 %-5s 国家 %2s 延迟 %3s ms 速度 %2.1f MB/s%s\n", ip, strconv.Itoa(port), country, tcplatency, speed, strings.Repeat(" ", 12))
if *multipleNum == 1 || *speedTestThreads < 5 {
// 输出结果
if speed >= *speedLimit/2 {
// 速度达到限速一半才在命令行输出
fmt.Printf("-IP %-15s 端口 %-5s 国家 %2s 延迟 %3s ms 速度 %4.1f MB/s%s\n", ip, strconv.Itoa(port), country, tcplatency, speed, strings.Repeat(" ", 1))
}