├── .github └── workflows │ └── create_release.yml ├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── ipscanner └── README.md ├── main.go ├── task ├── .gitignore ├── getASN.go ├── gptTest.go ├── linux.go ├── processCIDR.go ├── unZip2txtFile.go ├── update.go └── windows.go └── tool ├── QueryDownload.go ├── go.mod ├── go.sum ├── gptProxyTest.go ├── gptTest.go ├── makefile ├── mergeCSV.go ├── processFofaCSV.go ├── reParseResultTxtFile.go ├── tcpTest.go └── tcpTestWithProxy.go /.github/workflows/create_release.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/release.yml 2 | name: build and release 3 | 4 | on: 5 | pull_request: 6 | push: 7 | # run only against tags 8 | tags: 9 | - "*" 10 | release: 11 | types: [created] 12 | 13 | permissions: 14 | contents: write 15 | # packages: write 16 | # issues: write 17 | # id-token: write 18 | 19 | jobs: 20 | goreleaser: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Set up Go 28 | uses: actions/setup-go@v5 29 | with: 30 | go-version: stable 31 | 32 | - name: Debug Environment Variables 33 | run: echo "USE_EMOJI=$USE_EMOJI_TITLE" 34 | 35 | # More assembly might be required: Docker logins, GPG, etc. 36 | # It all depends on your needs. 37 | - name: Run GoReleaser 38 | uses: goreleaser/goreleaser-action@v6 39 | with: 40 | # either 'goreleaser' (default) or 'goreleaser-pro' 41 | distribution: goreleaser 42 | # 'latest', 'nightly', or a semver 43 | version: "~> v2" 44 | args: release --clean 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution 48 | # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 项目和插件的二进制文件 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.ini 8 | 9 | *test*.* 10 | *_temp*.* 11 | *_tmp*.* 12 | *cache/ 13 | 14 | # Test binary, built with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | *.log 20 | *.csv 21 | *.rar 22 | *.txt 23 | *.zip 24 | *.bat 25 | locations*.json 26 | 27 | 28 | # Dependency directories (remove the comment below to include it) 29 | vendor/ 30 | pkd/ 31 | 32 | # Go workspace file 33 | go.work 34 | 35 | # 编辑器文件夹 36 | .idea/ 37 | .vscode/ 38 | */.vscode/ 39 | */.idea/ 40 | build/ 41 | bin/ 42 | temp/ 43 | test/ 44 | ipLAB/ 45 | # ipscanner/ 46 | FofaCSV/ 47 | FofaCSVOutput/ 48 | dist/ 49 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # .goreleaser.yaml 2 | version: 2 3 | env: 4 | - GO111MODULE=on 5 | 6 | before: 7 | hooks: 8 | - go mod tidy 9 | - go generate ./... 10 | 11 | snapshot: 12 | version_template: "{{ incpatch .Version }}-next" 13 | 14 | builds: 15 | - id: bestipTest 16 | binary: bestipTest 17 | env: 18 | - CGO_ENABLED=0 19 | targets: 20 | - windows_amd64 21 | - linux_amd64 22 | - linux_arm64 23 | - darwin_amd64 24 | - darwin_arm64 25 | 26 | # archives: 27 | # - format: tar.gz 28 | # name_template: >- 29 | # {{ .ProjectName }}_ 30 | # {{- title .Os }}_ 31 | # {{- if eq .Arch "amd64" }}x86_64 32 | # {{- else if eq .Arch "386" }}i386 33 | # {{- else }}{{ .Arch }}{{ end }} 34 | # {{- if .Arm }}v{{ .Arm }}{{ end }} 35 | # format_overrides: 36 | # - goos: windows 37 | # format: zip 38 | # files: 39 | # - README.md 40 | # - LICENSE 41 | # - configs/* 42 | # - data/* 43 | 44 | archives: 45 | - format: binary # 设置格式为 `binary`,直接输出二进制 46 | name_template: >- 47 | {{- if eq .Os "windows" -}} 48 | {{ .Binary }} 49 | {{- else -}} 50 | {{ .Binary }}_{{ title .Os }}_{{ if eq .Arch "amd64" }}x86_64{{ else }}{{ .Arch }}{{ end }} 51 | {{- end }} 52 | checksum: 53 | name_template: "checksums.txt" 54 | 55 | changelog: 56 | sort: asc # 按提交时间升序排序,最早的提交显示在最前面 57 | use: github 58 | filters: 59 | exclude: 60 | - "^test(:|\\()" # 排除测试相关提交 61 | - "merge conflict" # 排除冲突合并记录 62 | - "^Merge (pull request|remote-tracking branch|branch)" # 排除合并请求 63 | - "go mod tidy" # 排除依赖清理提交 64 | - "^更新文件" 65 | - "^测试(:|\\()" 66 | 67 | # 根据正则表达式和标题分组提交消息 68 | # 69 | # 顺序值定义分组顺序 70 | # 未提供正则表达式意味着所有提交都将分组到默认分组下 71 | # 72 | # 匹配仅对提交消息的第一行执行,前缀为提交 SHA1,通常采用 `[:] ` 形式 73 | # 当使用 github-native 时,分组被禁用,因为它已经按自身分组 74 | # 正则表达式使用 RE2 语法,如下所示:https://github.com/google/re2/wiki/Syntax 75 | groups: 76 | # feat(UI): 新增用户登录界面、新增: 实现用户认证功能。 77 | - title: "New Features" # “新功能” 78 | # - title: "✨ New Features (新功能)" 79 | regexp: '^(?i).*?(feat|功能|新增|feature|新特性)(\(.+\))??!?[-:\s].+$' 80 | order: 100 81 | 82 | 83 | # fix: 修复UI组件加载延迟问题 84 | - title: "Bug fixes" # “Bug 修复” 85 | # - title: "🐛 Bug Fixes (修复问题)" 86 | regexp: '^(?i).*?(fix|修复|bug|错误|bugfix)(\(.+\))??!?[-:\s].+$' 87 | order: 200 88 | 89 | # config: 更新环境变量配置文件。 90 | - title: "Config updates" # “配置更新” 91 | # - title: "⚙️ Config Updates (配置更新)" 92 | regexp: '^(?i).*?(config|配置|configuration)(\(.+\))??!?[-:\s].+$' 93 | order: 300 94 | 95 | # sec: 修复 XSS 漏洞,增强安全策略。 96 | - title: "Security updates" # “安全更新” 97 | # - title: "🔒 Security Updates (安全更新)" 98 | regexp: '^(?i).*?(sec|安全|security)(\(.+\))??!?[-:\s].+$' 99 | order: 400 100 | 101 | # refactor: 代码重构,版本更新。 102 | - title: "Code Refactor" # “代码重构(既不是增加feature,也不是修复bug)” 103 | # - title: "🚀 Code Refactor (代码重构)" 104 | regexp: '^(?i).*?(refactor|重构|major|大版本)(\(.+\))??!?[-:\s].+$' 105 | order: 500 106 | 107 | # pref: 优化用户登录流程,减少加载时间。 108 | - title: "Code Optimization" # “性能优化” 109 | # - title: "⚡️ Code Optimization (性能优化)" 110 | regexp: '^(?i).*?(perf?|performance|优化|性能|opt?|optimization)(\(.+\))??!?[-:\s].+$' 111 | order: 600 112 | 113 | # deps: 更新依赖库版本,提升稳定性。 114 | - title: "Dependency updates" # “依赖更新” 115 | # - title: "📦 Dependency Updates (依赖更新)" 116 | regexp: '^(?i).*?(deps|依赖|dependency)(\(.+\))??!?[-:\s].+$' 117 | order: 700 118 | 119 | # docs: 更新API使用说明,添加示例代码。 120 | - title: "Documentation updates" # “文档更新” 121 | # - title: "📝 Documentation Updates (文档更新)" 122 | regexp: '^(?i).*?(docs?|文档|说明|documentation|注释)(\(.+\))??!?[-:\s].+$' 123 | order: 800 124 | 125 | # i18n: 添加西班牙语翻译,完善国际化支持。 126 | - title: "Translation updates" # “翻译更新” 127 | # - title: "🌐 Translation Updates (翻译更新)" 128 | regexp: '^(?i).*?(local?|i18n|翻译|localization|国际化|本地化)(\(.+\))??!?[-:\s].+$' 129 | order: 900 130 | 131 | # ci(build): 添加持续集成任务,自动构建和发布。 132 | - title: "Build process updates" # “构建过程更新” 133 | # - title: "🛠 Build Process Updates (构建过程更新)" 134 | regexp: '^(?i).*?(build|ci|构建|持续集成)(\(.+\))??!?[-:\s].+$' 135 | order: 1000 136 | 137 | # test: 增加单元测试用例,覆盖率提升到 85%。 138 | - title: "Test-related" # “测试相关” 139 | # - title: "✅ Test-Related(测试相关)" 140 | regexp: '^(?i).*?(test|测试|单元测试|集成测试)(\(.+\))??!?[-:\s].+$' 141 | order: 1100 142 | 143 | # style: 代码风格、格式变动(不影响代码逻辑) 144 | - title: "Style changes" # “格式变动” 145 | # - title: "🎨 Style Changes (样式变更)" 146 | regexp: '^(?i).*?(style|样式|format|格式)(\(.+\))??!?[-:\s].+$' 147 | order: 1200 148 | 149 | # style: 回滚提交 150 | - title: "Revert commit" # “回滚提交” 151 | # - title: " 🔙 revert commites (回滚提交)" 152 | regexp: '^(?i).*?(revert|回滚)(\(.+\))??!?[-:\s].+$' 153 | order: 1300 154 | 155 | # chore: 更新项目文档索引,整理文件结构。 156 | - title: "Other work" # “其他工作” 157 | # - title: "🛠 Other Work (其他工作)" 158 | order: 9999 159 | 160 | release: 161 | name_template: "v{{ .Version }} {{.TagSubject}}" 162 | 163 | replace_existing_artifacts: true 164 | mode: replace 165 | 166 | header: | 167 | 168 | **Latest Update:** 169 | 170 | > {{.TagContents}} 171 | 172 | Released at {{ .CommitDate }} 173 | 174 | 175 | footer: | 176 | **Full Changelog**: https://github.com/sinspired/ip-scanner/compare/{{ .PreviousTag }}...{{ if .IsNightly }}nightly{{ else }}{{ .Tag }}{{ end }} 177 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2024, Sinspired 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudflareBestIP 2 | 3 | [![Go Version](https://img.shields.io/github/go-mod/go-version/sinspired/CloudflareBestIP?logo=go&label=Go)](https://github.com/sinspired/CloudflareBestIP) 4 | [![Release Version](https://img.shields.io/github/v/release/sinspired/CloudflareBestIP?display_name=tag&logo=github&label=Release)](https://github.com/sinspired/CloudflareBestIP/releases/latest) 5 | [![GitHub repo size](https://img.shields.io/github/repo-size/sinspired/CloudflareBestIP?logo=github) 6 | ](https://github.com/sinspired/CloudflareBestIP) 7 | [![GitHub last commit](https://img.shields.io/github/last-commit/sinspired/CloudflareBestIP?logo=github&label=最后提交:)](https://github.com/sinspired/CloudflareBestIP) 8 | 9 | [![build and release](https://github.com/sinspired/CloudflareBestIP/actions/workflows/create_release.yml/badge.svg)](https://github.com/sinspired/CloudflareBestIP/actions/workflows/create_release.yml) 10 | 11 | CloudflareBestIP 采用go编写的小工具。能够自动下载知名的几个ip库,自适应识别文件格式进行测速优选。 12 | 13 | 如设置了domain和token,优选ip结果可直接上传到云端,实现自动化更新。 14 | 15 | 支持跨平台编译。 16 | 17 | # 安装 18 | 19 | 首先安装 Golang 和 Git,然后在终端中运行以下命令: 20 | 21 | ```bash 22 | git clone https://github.com/sinspired/CloudflareBestIP.git 23 | cd CloudflareBestIP 24 | go build -o BestipTest.exe main.go 25 | ``` 26 | 27 | 这将编译可执行文件 BestipTest.exe。 28 | 29 | # 参数说明 30 | 31 | **CloudflareBestIP** 可以接受以下参数: 32 | 33 | * -ip 直接检测ip地址 34 | * -file IP地址文件名称(*.txt或*.zip) (default "txt.zip") 35 | * -outfile 输出文件名称(自动设置) (default "result.csv") 36 | * -port 默认端口 (default 443) 37 | * -num 测速结果数量 (default 6) 38 | * -dlall 为true时检查ip库中的文件并依次下载 39 | * -speedlimit 最低下载速度(MB/s) (default 4) 40 | * -max 并发请求最大协程数 (default 1000) 41 | * -speedtest 下载测速协程数量,设为0禁用测速 (default 1) 42 | * -tcplimit TCP最大延迟(ms) (default 1000) 43 | * -httplimit HTTP最大延迟(ms) (default 1000) 44 | * -iplib 为true时检查ip库中的文件并依次下载 (default false) 45 | * -mulnum 多协程测速造成测速不准,可进行倍数补偿 (default 1) 46 | * -tls 是否启用TLS (default true) 47 | * -url 测速文件地址 (default "speed.cloudflare.com/__down?bytes=500000000") 48 | * -country 国家代码(US,SG,JP,DE...),以逗号分隔,留空时检测所有 49 | * -not 排除的国家代码(US,SG,JP,DE...) 50 | * -domain 上传地址,默认为空,用Text2KV项目建立的简易文件存储storage.example.com (default "") 51 | * -token 上传地址的Text2KV项目token(default "") 52 | * -api ip信息查询api,免费申请,api.ipapi.is,如留空则使用免费接口 (default "") 53 | 54 | 命令行键入 `-h` `help` 获取帮助 `./BestipTest.exe -h` 55 | 56 | # 运行 57 | 58 | 在终端中运行以下命令来启动程序: 59 | 60 | ### 不带参数运行 61 | 62 | Linux 63 | 64 | ```shell 65 | ./bestipTest_linux_amd64 66 | ``` 67 | 68 | Windows 69 | 70 | ```pwsh 71 | ./BestipTest.exe 72 | ``` 73 | 74 | 默认参数会检测网络情况(请关闭代理),之后会自动下载默认ip库并自动测速 75 | 76 | ### 设置参数 77 | 78 | ```pwsh 79 | ./BestipTest.exe -tcplimit=300 -httplimit=300 -speedlimit=5 -tls=true -port=443 -iplib=false -max=1000 -speedtest=5 -file="txt.zip" -outfile="result_源文件名.csv" -num=10 -dlall=false -countries="US,Sg,DE" -not="HK" -domain="" -token="" -api="" 80 | ``` 81 | 82 | 请替换参数值以符合您的实际需求。 83 | 84 | 85 | **注意:** 86 | 87 | `-domain="x.xxx.com"` 和 `-token="password"`,当优选结果 >0 时会提示是否上传优选ip结果到云端,需要输入域名和token。可以参考 自行搭建文件存储服务 88 | 89 | `api` 请自行至 申请,用于获取优选IP的ASN信息 90 | 91 | **文件格式:** 92 | 93 | 支持txt格式和zip格式,部分zip文件程序可以自动解压、合并、去重。支持的ip格式如下: 94 | 95 | * 单行ip的txt文件,程序按照指定的端口进行检测 96 | * `ip:port` 格式的txt文件,程序会识别ip和端口号 97 | * CIDR 格式的ip文件 98 | * 如果是从FOFA等网站下载的文件,可以把文件名设置为`ASN-Tls-PORT.txt`,把多个文件打包成一个`zip`文件,程序可以直接识别解压,然后根据文件名的tls状态和端口号检测。Tls 的值为0或1,对应false/true。 99 | * 使用参数 `-ip=""` 检测 `ip` 或 `ip:port` 格式的ip,多个IP使用 `,` 分割 100 | 101 | **命名规范:** 102 | 103 | `-file` 参数应遵循以下命名规范 104 | 105 | * txt文件,可命名为 ip_filename.txt,程序会识别"_"切出filename,以便设置输出文件名 result_filename.csv 106 | * zip文件,直接filename.zip 107 | * -outfile,建议使用 `result_"源文件名".csv` 格式 108 | 109 | **输出结果:** 110 | 111 | 优化了命令行界面输出,可以直观查看程序执行情况,优选ip结果存入 `result_"源文件名".csv` 中。 112 | 113 | 关于 ASN 信息 ,可自行至 https://ipapi.is/ 申请api,程序默认会判断优选 iP 的iso及company信息,会对IP类型进行简单判断。比如“商宽”、“机房”等。 114 | 115 | 如顺利获取 asn 信息,理想的 IP 国家格式将被设置为:`US-Uni商务-CF-209242丨FileName`,未获取到 asn 信息会回退为 `US` 116 | 117 | 118 | # 最新发行版下载 119 | 120 | [![Release Detail](https://img.shields.io/github/v/release/sinspired/CloudflareBestIP?sort=date&display_name=release&logo=github&label=Release)](https://github.com/sinspired/CloudflareBestIP/releases/latest) 121 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sinspired/CloudflareBestIP 2 | 3 | go 1.22.4 4 | 5 | require ( 6 | github.com/VividCortex/ewma v1.2.0 7 | github.com/mattn/go-ieproxy v0.0.12 8 | golang.org/x/sys v0.22.0 9 | golang.org/x/text v0.16.0 10 | ) 11 | 12 | require golang.org/x/net v0.25.0 // indirect 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= 2 | github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= 3 | github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M= 4 | github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c= 5 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 6 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 7 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 8 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 9 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 10 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 11 | -------------------------------------------------------------------------------- /ipscanner/README.md: -------------------------------------------------------------------------------- 1 | # cloudflare 2 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "encoding/csv" 7 | "encoding/json" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "net" 12 | "net/http" 13 | "os" 14 | "os/exec" 15 | "regexp" 16 | "runtime" 17 | "sort" 18 | "strconv" 19 | "strings" 20 | "sync" 21 | "sync/atomic" 22 | "time" 23 | 24 | "github.com/VividCortex/ewma" 25 | "github.com/mattn/go-ieproxy" 26 | "golang.org/x/text/cases" 27 | "golang.org/x/text/language" 28 | 29 | "github.com/sinspired/CloudflareBestIP/task" 30 | ) 31 | 32 | // 定义终端命令行变量 33 | var ( 34 | DirectIP = flag.String("ip", "", "直接检测ip地址") // IP地址名称 35 | File = flag.String("file", "txt.zip", "IP地址文件名称(*.txt或*.zip)") // IP地址文件名称 36 | outFile = flag.String("outfile", "result.csv", "输出文件名称(自动设置)") // 输出文件名称 37 | defaultPort = flag.Int("port", 443, "默认端口") // 端口 38 | maxThreads = flag.Int("max", 200, "并发请求最大协程数") // 最大协程数 39 | speedTestThreads = flag.Int("speedtest", 1, "下载测速协程数量,设为0禁用测速") // 下载测速协程数量 40 | speedLimit = flag.Float64("speedlimit", 5, "最低下载速度(MB/s)") // 最低下载速度 41 | speedTestURL = flag.String("url", "speed.cloudflare.com/__down?bytes=200000000", "测速文件地址") // 测速文件地址 42 | enableTLS = flag.Bool("tls", true, "是否启用TLS") // TLS是否启用 43 | multipleNum = flag.Float64("mulnum", 1, "多协程测速造成测速不准,可进行倍数补偿") // speedTest比较大时修改 44 | tcpLimit = flag.Int("tcplimit", 2000, "TCP最大延迟(ms)") // TCP最大延迟(ms) 45 | httpLimit = flag.Int("httplimit", 9999, "HTTP最大延迟(ms)") // HTTP最大延迟(ms) 46 | countryCodes = flag.String("country", "", "国家代码(US,SG,JP,DE...),以逗号分隔,留空时检测所有") // 国家代码数组 47 | ExcludedCounties = flag.String("not", "", "排除的国家代码(US,SG,JP,DE...)") // 排除的国家代码数组 48 | DownloadipLib = flag.Bool("iplib", false, "为true时检查ip库中的文件并依次下载") // 自动下载一些知名的反代IP列表 49 | Domain = flag.String("domain", "", "上传地址,默认为空,用Text2KV项目建立的简易文件存储storage.example.com") 50 | Token = flag.String("token", "", "上传地址的token,默认为空") 51 | RequestNum = flag.Int("num", 6, "测速结果数量") // 测速结果数量 52 | DownloadTestAll = flag.Bool("dlall", false, "为true对有效满足延迟检测的有效ip测速,可能需要很长时间") 53 | IPInfoApiKey = flag.String("api", "", "ip信息查询api,免费申请,api.ipapi.is,如留空则使用免费接口") 54 | CheckGPT = flag.Bool("gpt", false, "控制是否要检测可用于chatgpt的PROYIP,须使用非CloudFlare平台的代理") 55 | ) 56 | 57 | var ipLibs = map[string]string{ 58 | "txt.zip": "https://zip.baipiao.eu.org/", 59 | "baipiaoge.zip": "https://zip.baipiao.eu.org/", 60 | "ip_ProxyIPDB.txt": "https://ipdb.api.030101.xyz/?type=proxy", 61 | "ip_CFv4IPDB.txt": "https://ipdb.api.030101.xyz/?type=cfv4", 62 | "ip_BihaiCFIP.txt": "https://cfip.bihai.cf", 63 | "ip_BihaiCloudCFIP.txt": "https://cloudcfip.bihai.cf", 64 | } 65 | 66 | const ( 67 | bufferSize = 1024 68 | tcpTimeout = 2 * time.Second // 超时时间 69 | httpTimeout = 4 * time.Second // 超时时间 70 | maxDuration = 6 * time.Second // 最大延迟 71 | dlTimeout = 10 * time.Second // 下载超时 72 | ) 73 | 74 | var ( 75 | requestURL = "speed.cloudflare.com/cdn-cgi/trace" // 请求trace URL 76 | locationsJsonUrl = "https://speed.cloudflare.com/locations" // location.json下载 URL 77 | 78 | ) 79 | 80 | var ( 81 | startTime = time.Now() // 标记开始时间 82 | countries []string // 国家代码数组 83 | exceludedCountries []string // 排除的国家代码数组 84 | locationMap map[string]location // IP位置数据 85 | totalIPs int // IP总数 86 | countProcessedInt32 int32 // 延迟检测已处理进度计数,使用原子计数 87 | countAlive int // 延迟检测存活ip 88 | percentage float64 // 检测进度百分比 89 | totalResultChan []latencyTestResult // 存储延迟测速结果 90 | countQualified int // 存储合格ip数量 91 | baseLens int // 命令行输出基准长度 92 | apiKey string 93 | ) 94 | 95 | // 延迟检测结果结构体 96 | type latencyTestResult struct { 97 | ip string // IP地址 98 | port int // 端口 99 | tls bool // TLS状态 100 | dataCenter string // 数据中心 101 | region string // 地区 102 | country string // 国家 103 | city string // 城市 104 | latency string // http延迟 105 | // tcpDuration time.Duration // TCP请求延迟 106 | tcpDuration string // tcp延迟 107 | } 108 | 109 | // 下载测速结果结构体 110 | type speedTestResult struct { 111 | latencyTestResult 112 | downloadSpeed float64 // 下载速度 113 | } 114 | 115 | // 位置信息结构体 116 | type location struct { 117 | Iata string `json:"iata"` 118 | Lat float64 `json:"lat"` 119 | Lon float64 `json:"lon"` 120 | Cca2 string `json:"cca2"` 121 | Region string `json:"region"` 122 | City string `json:"city"` 123 | } 124 | 125 | // IPPort 结构体存储 IP 和端口信息 126 | type IPPort struct { 127 | IP string 128 | Port int 129 | } 130 | 131 | var ipPortList []IPPort // 全局变量,存储 IP:port 格式的数据 132 | 133 | // 尝试提升文件描述符的上限 134 | func increaseMaxOpenFiles() { 135 | if runtime.GOOS != "windows" { 136 | fmt.Println("正在尝试提升文件描述符的上限...") 137 | cmd := exec.Command("bash", "-c", "ulimit -n 10000") 138 | _, err := cmd.CombinedOutput() 139 | if err != nil { 140 | fmt.Printf("提升文件描述符上限时出现错误: %v\n", err) 141 | } else { 142 | fmt.Printf("文件描述符上限已提升!\n") 143 | } 144 | } else { 145 | fmt.Println("Windows系统不需要提升文件描述符上限") 146 | } 147 | } 148 | 149 | func clearScreen() { 150 | var cmd *exec.Cmd 151 | 152 | switch runtime.GOOS { 153 | case "windows": 154 | cmd = exec.Command("cmd", "/c", "cls") 155 | case "linux", "darwin": // Linux and macOS 156 | cmd = exec.Command("clear") 157 | default: 158 | fmt.Println("Unsupported platform") 159 | return 160 | } 161 | 162 | cmd.Stdout = os.Stdout 163 | err := cmd.Run() 164 | if err != nil { 165 | return 166 | } 167 | } 168 | 169 | // 功能函数 170 | 171 | // 检查location.json文件,如不存在,则从网络下载 172 | func locationsJsonDownload() { 173 | var locations []location // 创建location数组以存储json文件, 174 | if _, err := os.Stat("locations.json"); os.IsNotExist(err) { 175 | fmt.Println("正在从 " + locationsJsonUrl + " 下载 locations.json") 176 | 177 | body, err := downloadWithIEProxy(locationsJsonUrl) 178 | if err != nil { 179 | fmt.Printf("下载失败: %v\n", err) 180 | return 181 | } 182 | 183 | err = json.Unmarshal(body, &locations) 184 | if err != nil { 185 | fmt.Printf("无法解析JSON: %v\n", err) 186 | return 187 | } 188 | file, err := os.Create("locations.json") 189 | if err != nil { 190 | fmt.Printf("无法创建文件: %v\n", err) 191 | return 192 | } 193 | defer file.Close() 194 | 195 | _, err = file.Write(body) 196 | if err != nil { 197 | fmt.Printf("无法写入文件: %v\n", err) 198 | return 199 | } 200 | fmt.Println("\033[32m成功下载并创建 location.json\033[0m") 201 | file, err = os.Open("locations.json") 202 | if err != nil { 203 | fmt.Printf("无法打开文件: %v\n", err) 204 | return 205 | } 206 | defer file.Close() 207 | 208 | body, err = io.ReadAll(file) 209 | if err != nil { 210 | fmt.Printf("无法读取文件: %v\n", err) 211 | return 212 | } 213 | 214 | err = json.Unmarshal(body, &locations) 215 | if err != nil { 216 | fmt.Printf("无法解析JSON: %v\n", err) 217 | return 218 | } 219 | } else { 220 | fmt.Println("\033[0;90m本地 locations.json 已存在,无需重新下载\033[0m") 221 | file, err := os.Open("locations.json") 222 | if err != nil { 223 | fmt.Printf("无法打开文件: %v\n", err) 224 | return 225 | } 226 | defer file.Close() 227 | 228 | body, err := io.ReadAll(file) 229 | if err != nil { 230 | fmt.Printf("无法读取文件: %v\n", err) 231 | return 232 | } 233 | 234 | err = json.Unmarshal(body, &locations) 235 | if err != nil { 236 | fmt.Printf("无法解析JSON: %v\n", err) 237 | return 238 | } 239 | } 240 | 241 | // 读取位置数据并存入变量 242 | locationMap = make(map[string]location) 243 | for _, loc := range locations { 244 | locationMap[loc.Iata] = loc 245 | } 246 | } 247 | 248 | // downloadWithIEProxy 尝试使用IE代理设置下载文件 249 | func downloadWithIEProxy(downloadURL string) ([]byte, error) { 250 | proxyFunc := ieproxy.GetProxyFunc() 251 | client := &http.Client{ 252 | Timeout: 15 * time.Second, 253 | Transport: &http.Transport{Proxy: proxyFunc}, 254 | } 255 | 256 | resp, err := client.Get(downloadURL) 257 | if err != nil { 258 | return nil, fmt.Errorf("下载时出错: %v", err) 259 | } 260 | defer resp.Body.Close() 261 | 262 | if resp.StatusCode != http.StatusOK { 263 | body, _ := io.ReadAll(resp.Body) // 尝试读取响应体以获取更多错误信息 264 | return nil, fmt.Errorf("非预期的HTTP状态码: %v, 响应体: %s", resp.Status, string(body)) 265 | } 266 | 267 | return io.ReadAll(resp.Body) 268 | } 269 | 270 | // autoNetworkDetection 自动检测网络环境,返回一个bool值 271 | func autoNetworkDetection() bool { 272 | // 检查系统代理是否启用 273 | if task.CheckProxyEnabled() { 274 | fmt.Println("\033[2J\033[0;0H\033[31m检测到系统代理已启用,请关闭VPN后重试。\033[0m") 275 | return false 276 | } else { 277 | fmt.Println("\033[90m系统代理未启用,检测tun模式代理……\033[0m") 278 | 279 | // 检查Google.com是否可访问 280 | if checkProxyUrl("https://www.google.com") { 281 | fmt.Println("\033[31m已开启tun模式代理,可以访问外网,请关闭VPN后重试。\033[0m") 282 | return false 283 | } else { 284 | fmt.Println("\033[90m未开启vpn,检测墙内网络是否正常……\033[0m") 285 | } 286 | } 287 | 288 | // 检测Baidu是否可访问 289 | if !checkNormalUrl("https://www.baidu.com") { 290 | fmt.Println("\033[2J\033[0;0H\033[31m无互联网访问,请检查网络连接。\033[0m") 291 | return false 292 | } else { 293 | // 清除输出内容 294 | fmt.Print("\033[2J\033[0;0H") 295 | fmt.Printf("\033[32m网络环境检测正常 \033[0m\n") 296 | } 297 | return true 298 | } 299 | 300 | // 代码转移到 task/windows.go task/linux.go 以支持跨系统编译 301 | // // checkProxyEnabled 检测是否开启系统代理服务器 302 | // func checkProxyEnabled() bool { 303 | // if runtime.GOOS == "windows" { 304 | // k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE) 305 | // if err != nil { 306 | // fmt.Println("无法打开注册表键:", err) 307 | // return false 308 | // } 309 | // defer k.Close() 310 | 311 | // proxyEnable, _, err := k.GetIntegerValue("ProxyEnable") 312 | // if err != nil { 313 | // fmt.Println("无法读取ProxyEnable值:", err) 314 | // return false 315 | // } 316 | 317 | // return proxyEnable == 1 318 | // } 319 | 320 | // // 对于非Windows系统,我们可以检查环境变量 321 | // return os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" 322 | // } 323 | 324 | // checkNormalUrl 尝试连接指定的URL,检查网络是否可访问 325 | func checkNormalUrl(url string) bool { 326 | resp, err := http.Get(url) 327 | if err != nil { 328 | // fmt.Printf("访问 %s 时未知错误:[ %v ]\n", url, err) 329 | return false 330 | } 331 | defer resp.Body.Close() 332 | // fmt.Println("检测可以ping通:" + url) 333 | return true 334 | } 335 | 336 | // checkProxyUrl 根据域名检测连通性,自动检测代理服务器. 337 | func checkProxyUrl(urlStr string) bool { 338 | proxyFunc := ieproxy.GetProxyFunc() 339 | client := &http.Client{ 340 | Timeout: 2 * time.Second, 341 | Transport: &http.Transport{Proxy: proxyFunc}, 342 | } 343 | 344 | resp, err := client.Get(urlStr) 345 | if err != nil { 346 | // fmt.Printf("连通性错误 %s: %v\n", urlStr, err) 347 | return false 348 | } 349 | defer resp.Body.Close() 350 | 351 | // fmt.Println("成功连接: " + urlStr) 352 | return true 353 | } 354 | 355 | // 检查IP库是否存在并执行下载 356 | func checkIPLib(url string, fileName string) { 357 | fileInfo, err := os.Stat(fileName) 358 | if os.IsNotExist(err) { 359 | // 文件不存在,直接下载 360 | fmt.Printf("\033[31mip库文件 %s 不存在,正在下载...\033[0m\n", fileName) 361 | downloadFile(url, fileName) 362 | } else { 363 | // 文件存在,检查创建时间 364 | fileModTime := fileInfo.ModTime() 365 | if time.Since(fileModTime) > 12*time.Hour { 366 | // 文件超过12小时,重新下载 367 | fmt.Printf("\033[31mip库文件 %s 超过12小时,正在重新下载...\033[0m\n", fileName) 368 | downloadFile(url, fileName) 369 | } else { 370 | // fmt.Printf("ip库文件 %s 存在且未超过12小时,无需下载\n",fileName) 371 | } 372 | } 373 | } 374 | 375 | // 文件下载函数 376 | func downloadFile(url, fileName string) { 377 | // 创建文件 378 | out, err := os.Create(fileName) 379 | if err != nil { 380 | fmt.Println("无法创建文件:", err) 381 | return 382 | } 383 | defer out.Close() 384 | 385 | // 获取数据 386 | resp, err := http.Get(url) 387 | if err != nil { 388 | fmt.Println("下载失败:", err) 389 | return 390 | } 391 | defer resp.Body.Close() 392 | 393 | // 写入文件 394 | _, err = io.Copy(out, resp.Body) 395 | if err != nil { 396 | fmt.Println("写入文件失败:", err) 397 | } 398 | fmt.Println("下载状态...........................................[\033[32mok\033[0m]\n") 399 | } 400 | 401 | // 测速结果输出文件重命名函数 402 | func resetOutFileName(File string) { 403 | if strings.Contains(File, "_") { 404 | FileName := strings.Split(File, ".")[0] // 去掉后缀名 405 | resultName := strings.Split(FileName, "_")[1] // 分离名字字段 406 | caser := cases.Title(language.English) // 使用English作为默认语言标签 407 | resultName = caser.String(resultName) // 首字母大写 408 | 409 | if *outFile == "result.csv" { 410 | // 如果输出文件名为默认,即未指定 411 | *outFile = "result_" + resultName + ".csv" 412 | } 413 | 414 | } else if File == "txt.zip" { 415 | // 默认反代IP列表 416 | if *outFile == "result.csv" { 417 | *outFile = "result_Baipiaoge.csv" 418 | } 419 | } else if File == "ip.txt" { 420 | // 默认ip列表 421 | if *outFile == "result.csv" { 422 | *outFile = "result_Test.csv" 423 | } 424 | } else { 425 | FileName := strings.Split(File, ".")[0] // 去掉后缀名 426 | caser := cases.Title(language.English) // 使用English作为默认语言标签 427 | FileName = caser.String(FileName) // 首字母大写 428 | 429 | if *outFile == "result.csv" { 430 | *outFile = "result_" + FileName + ".csv" 431 | } 432 | } 433 | } 434 | 435 | // getUniqueIPs 处理 IP 列表并去重,返回唯一 IP 的切片。 436 | func getUniqueIPs(content []byte) []string { 437 | // 将内容按行分割成 IP 列表 438 | ips := strings.Split(string(content), "\n") 439 | ipSet := make(map[string]struct{}) 440 | 441 | // 遍历 IP 列表,去重 442 | for _, ip := range ips { 443 | if ip != "" { 444 | ipSet[ip] = struct{}{} 445 | } 446 | } 447 | 448 | // 将集合转换回切片 449 | uniqueIPs := make([]string, 0, len(ipSet)) 450 | for ip := range ipSet { 451 | uniqueIPs = append(uniqueIPs, ip) 452 | } 453 | return uniqueIPs 454 | } 455 | 456 | // pause 暂停程序,等待用户按任意键继续。 457 | func pause() { 458 | fmt.Println("按任意键继续...") 459 | fmt.Scanln() 460 | } 461 | 462 | // processASNZipedFiles 处理文件格式为“ASN-Tls-Port.txt”格式的压缩包文件。 463 | // 它计算所有唯一 IP 的总数,并检测有效的 IP。 464 | func processASNZipedFiles(fileInfos []task.FileInfo) { 465 | totalAliveIPs := 0 466 | totalIPs = 0 467 | 468 | // 遍历每个文件信息,处理 IP 列表 469 | for _, info := range fileInfos { 470 | // 获取唯一的 IP 列表 471 | uniqueIPs := getUniqueIPs(info.Content) 472 | totalIPs += len(uniqueIPs) 473 | } 474 | fmt.Println("\n成功获取去重ip列表,开始TCP/HTTP延迟检测...\n") 475 | // 检测每个文件中的有效 IP 476 | for _, info := range fileInfos { 477 | // 获取唯一的 IP 列表 478 | uniqueIPs := getUniqueIPs(info.Content) 479 | // 延迟检测 IP 是否有效 480 | aliveCount := delayedDetectionIPs(uniqueIPs, info.TLS, info.Port) 481 | totalAliveIPs += aliveCount 482 | } 483 | 484 | // 如果没有发现有效的 IP,输出提示并退出程序 485 | if len(totalResultChan) == 0 { 486 | fmt.Println("\033[31m没有发现有效的 IP\033[0m") 487 | os.Exit(0) 488 | } 489 | } 490 | 491 | // 处理单个ip列表,txt格式 492 | func processIPListFile(fileName string) { 493 | totalAliveIPs := 0 494 | 495 | ips, err := readIPs(fileName) 496 | if err != nil { 497 | fmt.Printf("读取 IP 时出错: %v\n", err) 498 | return 499 | } 500 | 501 | totalIPs = len(ips) + len(ipPortList) // 总ip数,包括单行ip和带端口ip数 502 | if totalIPs == 0 { 503 | // 未读取ip退出程序 504 | fmt.Println("\033[31m未读取到IP数据\033[0m ") 505 | os.Exit(0) 506 | } else if len(ipPortList) == 0 { 507 | // 如果带端口ip数为0,则直接检测单行ip数组 508 | aliveCount := delayedDetectionIPs(ips, *enableTLS, *defaultPort) 509 | totalAliveIPs += aliveCount 510 | } else { 511 | for _, ip := range ips { 512 | // 混杂的情况,把单行ip加上默认端口添加到ipPortList数组中统一处理 513 | ipPortList = append(ipPortList, IPPort{ip, *defaultPort}) 514 | } 515 | var ipsPortListStr []string // delayedDetectionIPs接受的结构 516 | for _, ipPort := range ipPortList { 517 | // 将 ipPortList 转换为 []string,{ip:port}格式,并逐条追加到 ipsPortListStr 列表中 518 | ipsPortListStr = append(ipsPortListStr, fmt.Sprintf("%s:%d", ipPort.IP, ipPort.Port)) 519 | } 520 | 521 | // ipsPortListStr 已包含了端口,所以就不需要传入端口 522 | aliveCount := delayedDetectionIPs(ipsPortListStr, *enableTLS, 0) 523 | totalAliveIPs += aliveCount 524 | } 525 | 526 | if len(totalResultChan) == 0 { 527 | fmt.Println("\033[31m没有发现有效的IP\033[0m ") 528 | os.Exit(0) 529 | } 530 | } 531 | 532 | // logWithProgress 显示进度条 533 | func logWithProgress(countProcessed, total int) { 534 | if total != 0 { 535 | percentage := float64(countProcessed) / float64(total) * 100 536 | // barLength := 29 // 进度条长度 537 | // baseLens = len("-IP 64.110.104.30 端口 443 位置:JP KIX 延迟 158 ms [ 489 ms ]") 538 | baseLens = len(fmt.Sprintf("-IP %-15s 端口 %-5d 位置:%2s %3s 延迟 %3d ms [ %-3d ms ]\n", "255.255.255.255", 65001, "US", "SJX", 999, 999)) 539 | totalCountLen := len(strconv.Itoa(totalIPs)) 540 | alivelen := (totalCountLen - 1) 541 | if (totalCountLen-2)*2+1 >= (totalCountLen - 1) { 542 | alivelen = (totalCountLen - 2) * 2 543 | } 544 | barLength := baseLens - len("进度") - len("100.00% / 存活 IP: /") - totalCountLen*2 - alivelen - 3 545 | progress := int(percentage / 100 * float64(barLength)) 546 | 547 | // 构建进度条 548 | bar := fmt.Sprintf("\033[32m%s\033[0m%s", strings.Repeat("■", progress), strings.Repeat("▣", barLength-progress)) 549 | // 并发检测进度显示 550 | 551 | 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) 552 | } 553 | } 554 | 555 | // delayedDetectionIPs 对给定的 IP 列表进行延迟检测,并返回存活 IP 的数量。 556 | // 参数: 557 | // - ips: 待检测的 IP 列表。 558 | // - enableTLS: 是否启用 TLS。 559 | // - port: 端口号。 560 | func delayedDetectionIPs(ips []string, enableTLS bool, port int) int { 561 | // 获取文件名中是否有 TLS 信息 562 | fileName := strings.Split(*File, ".")[0] 563 | if strings.Contains(fileName, "-") { 564 | TlsStatus := strings.Split(fileName, "-")[1] 565 | if TlsStatus == "0" { 566 | enableTLS = false 567 | } 568 | } 569 | 570 | var wg sync.WaitGroup 571 | wg.Add(len(ips)) 572 | 573 | // 创建一个长度为输入数据长度的通道 574 | resultChan := make(chan latencyTestResult, len(ips)) 575 | thread := make(chan struct{}, *maxThreads) 576 | total := totalIPs // IP 数据总数 577 | 578 | for _, ip := range ips { 579 | thread <- struct{}{} 580 | go func(ip string) { 581 | defer func() { 582 | <-thread 583 | wg.Done() 584 | // countProcessed++ // 已处理 IP 数计数器 585 | atomic.AddInt32(&countProcessedInt32, 1) // 使用原子操作增加计数器 586 | countProcessedInt := int(atomic.LoadInt32(&countProcessedInt32)) 587 | // 调用logWithProgress 函数输出进度信息 588 | logWithProgress(countProcessedInt, total) 589 | fmt.Printf("存活 IP: \033[1;32;5m%d\033[0m\r", countAlive) 590 | }() 591 | 592 | // 如果 IP 格式为 ip:port,则分离 IP 和端口 593 | if strings.Contains(ip, ":") && strings.Count(ip, ":") == 1 { 594 | ipPort := strings.Split(ip, ":") 595 | if len(ipPort) == 2 { 596 | ipAddr := ipPort[0] 597 | portStr := ipPort[1] 598 | 599 | // 验证 IP 地址格式 600 | if net.ParseIP(ipAddr) == nil { 601 | fmt.Printf("无效 IP 地址: %s\n", ipAddr) 602 | return 603 | } 604 | // 验证端口格式 605 | if portStr == "" { 606 | ip = ipAddr 607 | port = *defaultPort // 使用默认端口 608 | } else { 609 | portInt, err := strconv.Atoi(portStr) 610 | if err != nil || portInt < 1 || portInt > 65535 { 611 | fmt.Printf("无效端口: %s\n", portStr) 612 | return 613 | } 614 | // 分离并验证成功,重新赋值 615 | ip = ipAddr 616 | port = portInt 617 | } 618 | } 619 | } 620 | 621 | // 检测平均延迟和丢包率 622 | var ( 623 | recv int 624 | totalDelay time.Duration 625 | PingTimes = 4 626 | ) 627 | for i := 0; i < PingTimes; i++ { 628 | if ok, delay := tcping(ip, port); ok { 629 | recv++ 630 | totalDelay += delay 631 | } 632 | } 633 | if recv == 0 { 634 | return 635 | } 636 | 637 | Sended := PingTimes 638 | Received := recv 639 | LossRate := float64((Sended - Received) / Sended) 640 | 641 | if LossRate >= 0.5 { 642 | // fmt.Printf("丢包率错误:%.1f\n", LossRate) 643 | return 644 | } 645 | tcpDuration := totalDelay / time.Duration(Received) 646 | 647 | // httping 延迟检测 648 | start := time.Now() 649 | client := http.Client{ 650 | Transport: &http.Transport{ 651 | // 使用 DialContext 函数 652 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 653 | return (&net.Dialer{ 654 | // Timeout: httpTimeout, 655 | // KeepAlive: 0, 656 | }).DialContext(ctx, network, net.JoinHostPort(ip, strconv.Itoa(port))) 657 | }, 658 | }, 659 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 660 | return http.ErrUseLastResponse // 阻止重定向 661 | }, 662 | Timeout: httpTimeout, 663 | } 664 | 665 | var protocol string 666 | if enableTLS { 667 | protocol = "https://" 668 | } else { 669 | protocol = "http://" 670 | } 671 | requestURL := protocol + requestURL 672 | req, _ := http.NewRequest(http.MethodGet, requestURL, nil) 673 | 674 | // 添加用户代理 675 | 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") 676 | req.Close = true 677 | resp, err := client.Do(req) 678 | if err != nil { 679 | // fmt.Println("http错误:", err) 680 | return 681 | } 682 | if resp.StatusCode != http.StatusOK { 683 | // fmt.Printf("ip:%s,状态码:%d \n", ip, resp.StatusCode) 684 | return 685 | } 686 | duration := time.Since(start) 687 | if duration > maxDuration { 688 | // fmt.Printf("http延时:%v,最大允许:%v \033[31m超时\033[0m] \n", duration, maxDuration) 689 | return 690 | } 691 | 692 | body, err := io.ReadAll(resp.Body) 693 | 694 | // var colo string 695 | // if resp.Header.Get("Server") == "cloudflare" { 696 | // str := resp.Header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC 697 | // colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str) 698 | // } else { 699 | // str := resp.Header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1 700 | // colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str) 701 | // } 702 | 703 | // pause() 704 | if strings.Contains(string(body), "uag=Mozilla/5.0") { 705 | if matches := regexp.MustCompile(`colo=([A-Z]{3})`).FindStringSubmatch(string(body)); len(matches) > 1 { 706 | dataCenter := matches[1] 707 | loc, ok := locationMap[dataCenter] 708 | // 排除的国家代码 709 | if len(exceludedCountries) != 0 && containsIgnoreCase(exceludedCountries, loc.Cca2) { 710 | return 711 | } 712 | // 根据 TCP 和 HTTP 延迟筛选检测结果 713 | if float64(tcpDuration.Milliseconds()) <= float64(*tcpLimit) && float64(duration.Milliseconds()) <= float64(*httpLimit) { 714 | // 根据国家代码筛选检测结果,如果为空,则不筛选 715 | if len(countries) == 0 || containsIgnoreCase(countries, loc.Cca2) { 716 | countAlive++ // 记录存活 IP 数量 717 | if ok { 718 | 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()) 719 | 720 | resultChan <- latencyTestResult{ip, port, enableTLS, dataCenter, loc.Region, loc.Cca2, loc.City, fmt.Sprintf("%d", duration.Milliseconds()), fmt.Sprintf("%d", tcpDuration.Milliseconds())} 721 | } else { 722 | fmt.Printf("-IP %-15s 端口 %-5d 位置信息未知 延迟 %-3d ms \n", ip, port, tcpDuration.Milliseconds()) 723 | 724 | resultChan <- latencyTestResult{ip, port, enableTLS, dataCenter, "", "", "", fmt.Sprintf("%d", duration.Milliseconds()), fmt.Sprintf("%d", tcpDuration.Milliseconds())} 725 | } 726 | } 727 | } else { 728 | // fmt.Printf("-IP %-15s 端口 %-5d 位置:%2s%3s tcp延迟 %3d ms http延迟 %3d [不合格] \n", ip, port, loc.Cca2, dataCenter, loc.City, tcpDuration.Milliseconds(), duration.Milliseconds()) 729 | } 730 | } 731 | } else { 732 | return 733 | } 734 | }(ip) 735 | } 736 | 737 | wg.Wait() 738 | close(resultChan) 739 | 740 | // 把通道里的内容添加到全局变量 totalResultChan 数组中,以便统一处理,增加效率 741 | for res := range resultChan { 742 | totalResultChan = append(totalResultChan, res) 743 | } 744 | // 并发检测执行完毕后输出信息 745 | if int(atomic.LoadInt32(&countProcessedInt32)) == total { 746 | time.Sleep(200 * time.Millisecond) 747 | countProcessedInt := int(atomic.LoadInt32(&countProcessedInt32)) 748 | logWithProgress(countProcessedInt, total) 749 | fmt.Printf("存活 IP: \033[1;32;5m%-3d\033[0m \r", countAlive) 750 | fmt.Printf("\n\nTCP/HTTP 延迟检测完成!\n") 751 | // pause() 752 | } 753 | return countAlive 754 | } 755 | 756 | // 从文件中读取IP地址并处理 757 | func readIPs(File string) ([]string, error) { 758 | file, err := os.Open(File) 759 | if err != nil { 760 | return nil, err 761 | } 762 | defer file.Close() 763 | 764 | // 创建一个 map 存储不重复的 IP 地址 765 | ipMap := make(map[string]struct{}) 766 | 767 | scanner := bufio.NewScanner(file) 768 | for scanner.Scan() { 769 | ipAddr := scanner.Text() 770 | if len(ipAddr) < 7 { 771 | continue 772 | } 773 | // 如果是碧海的ip库,把ip和端口之间的制表符替换为":"以便后续代码处理 774 | if strings.Count(ipAddr, "\t") == 1 { 775 | ipAddr = strings.Replace(ipAddr, "\t", ":", 1) 776 | } 777 | // 判断是否为 CIDR 格式的 IP 地址 778 | if strings.Contains(ipAddr, "/") && strings.Count(ipAddr, ":") != 1 && strings.Count(ipAddr, "#") != 1 { 779 | ip, ipNet, err := net.ParseCIDR(ipAddr) 780 | if err != nil { 781 | fmt.Printf("无法解析CIDR格式的IP: %v\n", err) 782 | continue 783 | } 784 | for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); inc(ip) { 785 | ipMap[ip.String()] = struct{}{} 786 | } 787 | } else if strings.Contains(ipAddr, ":") || strings.Contains(ipAddr, "#") { 788 | if strings.Count(ipAddr, ":") > 1 { 789 | // IPv6 地址 790 | ipMap[ipAddr] = struct{}{} 791 | } else if strings.Count(ipAddr, ":") == 1 { 792 | // 带端口的IP列表,以:分割ip与port,IP:port 格式 793 | if strings.Contains(ipAddr, "#") { 794 | ipPort := strings.Split(ipAddr, "#")[0] 795 | ip := strings.Split(ipPort, ":")[0] 796 | portStr := strings.Split(ipPort, ":")[1] 797 | portStr = strings.TrimSpace(portStr) 798 | port, err := strconv.Atoi(portStr) 799 | if err != nil { 800 | fmt.Printf("%s端口转换错误:%v\n", ipAddr, err) 801 | continue 802 | } 803 | // ipMap[ip] = struct{}{} 804 | ipPortList = append(ipPortList, IPPort{IP: ip, Port: port}) // 存储 IP:port 格式的数据 805 | } else { 806 | ip := strings.Split(ipAddr, ":")[0] 807 | portStr := strings.Split(ipAddr, ":")[1] 808 | port, err := strconv.Atoi(portStr) 809 | if err != nil { 810 | fmt.Println(ipAddr) 811 | fmt.Printf("%s端口转换错误:%v\n", ipAddr, err) 812 | continue 813 | } 814 | // ipMap[ip] = struct{}{} 815 | ipPortList = append(ipPortList, IPPort{IP: ip, Port: port}) // 存储 IP:port 格式的数据 816 | } 817 | } 818 | } else { 819 | ipMap[ipAddr] = struct{}{} 820 | } 821 | } 822 | 823 | // 将 map 的键转换回切片,获得去重的ip地址 824 | ips := make([]string, 0, len(ipMap)) 825 | for ip := range ipMap { 826 | ips = append(ips, ip) 827 | } 828 | 829 | fmt.Println("\n成功获取去重ip列表,开始TCP/HTTP延迟检测...\n") 830 | 831 | return ips, scanner.Err() 832 | } 833 | 834 | // inc函数实现ip地址自增 835 | func inc(ip net.IP) { 836 | for j := len(ip) - 1; j >= 0; j-- { 837 | ip[j]++ 838 | if ip[j] > 0 { 839 | break 840 | } 841 | } 842 | } 843 | 844 | // 检测反代ip是否被chatgpt拉黑 845 | func checkGptProxy() { 846 | println("检测反代ip是否可用于chatgpt") 847 | var wg sync.WaitGroup 848 | var count int32 849 | var gptProxyAlive int 850 | 851 | port := "443" 852 | 853 | // 创建一个长度为输入数据长度的通道 854 | var resultGptAlive []string 855 | thread := make(chan struct{}, *maxThreads) 856 | total := len(totalResultChan) 857 | 858 | wg.Add(len(totalResultChan)) 859 | for _, res := range totalResultChan { 860 | thread <- struct{}{} 861 | go func(res latencyTestResult) { 862 | defer func() { 863 | <-thread 864 | wg.Done() 865 | // countProcessed++ // 已处理 IP 数计数器 866 | atomic.AddInt32(&count, 1) // 使用原子操作增加计数器 867 | countProcessedInt := int(atomic.LoadInt32(&count)) 868 | // 调用logWithProgress 函数输出进度信息 869 | logWithProgress(countProcessedInt, total) 870 | fmt.Printf("存活 IP: \033[1;32;5m%d\033[0m\r", gptProxyAlive) 871 | }() 872 | 873 | gptAlive := task.CheckGptProxy(res.ip, port) 874 | if gptAlive { 875 | resultGptAlive = append(resultGptAlive, res.ip) 876 | gptProxyAlive++ 877 | println("找到一个哦!") 878 | } else { 879 | println("不合格") 880 | } 881 | 882 | }(res) 883 | } 884 | wg.Wait() 885 | if len(resultGptAlive) > 0 { 886 | for _, ip := range resultGptAlive { 887 | println("可以访问chatgpt的ip:%s", ip) 888 | } 889 | } else { 890 | println("没有可以访问chatgpt的proxyip") 891 | } 892 | 893 | pause() 894 | } 895 | 896 | // 下载测速函数 897 | func getDownloadSpeed(ip string, port int, enableTLS bool, latency string, tcplatency string, country string) float64 { 898 | var protocol string 899 | if enableTLS { 900 | protocol = "https://" 901 | } else { 902 | protocol = "http://" 903 | } 904 | fullURL := protocol + *speedTestURL 905 | 906 | // Create request 907 | req, err := http.NewRequest("GET", fullURL, nil) 908 | if err != nil { 909 | fmt.Printf("-IP %-15s 端口 %-5s 国家 %s \033[9;31m测速无效1\033[0m%s\n", ip, strconv.Itoa(port), country, strings.Repeat(" ", 18)) 910 | return 0.0 911 | } 912 | 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") 913 | 914 | // Mark start time 915 | // startTime := time.Now() 916 | 917 | // Create HTTP client 918 | client := &http.Client{ 919 | Transport: &http.Transport{ 920 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 921 | return (&net.Dialer{ 922 | // Timeout: httpTimeout, 923 | KeepAlive: dlTimeout, 924 | }).DialContext(ctx, network, net.JoinHostPort(ip, strconv.Itoa(port))) 925 | }, 926 | }, 927 | Timeout: dlTimeout, 928 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 929 | if len(via) > 10 { 930 | return http.ErrUseLastResponse 931 | } 932 | return nil 933 | }, 934 | } 935 | // timeStart := time.Now() // 开始时间(当前) 936 | // Send request 937 | response, err := client.Do(req) 938 | if err != nil { 939 | fmt.Printf("-IP %-15s 端口 %-5s 国家 %s \033[9;31m测速无效\033[0m%s\n", ip, strconv.Itoa(port), country, strings.Repeat(" ", 18)) 940 | return 0.0 941 | } 942 | defer response.Body.Close() 943 | 944 | if response.StatusCode != http.StatusOK { 945 | return 0.0 946 | } 947 | 948 | timeStart := time.Now() // 开始时间(当前) 949 | timeEnd := timeStart.Add(dlTimeout) // 加上下载测速时间得到的结束时间 950 | 951 | contentLength := response.ContentLength // 文件大小 952 | buffer := make([]byte, bufferSize) 953 | 954 | var ( 955 | contentRead int64 = 0 956 | timeSlice = dlTimeout / 100 957 | timeCounter = 1 958 | lastContentRead int64 = 0 959 | ) 960 | 961 | nextTime := timeStart.Add(timeSlice * time.Duration(timeCounter)) 962 | e := ewma.NewMovingAverage() 963 | 964 | // 循环计算,如果文件下载完了(两者相等),则退出循环(终止测速) 965 | for contentLength != contentRead { 966 | currentTime := time.Now() 967 | if currentTime.After(nextTime) { 968 | timeCounter++ 969 | nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter)) 970 | e.Add(float64(contentRead - lastContentRead)) 971 | lastContentRead = contentRead 972 | } 973 | // 如果超出下载测速时间,则退出循环(终止测速) 974 | if currentTime.After(timeEnd) { 975 | break 976 | } 977 | bufferRead, err := response.Body.Read(buffer) 978 | if err != nil { 979 | if err != io.EOF { // 如果文件下载过程中遇到报错(如 Timeout),且并不是因为文件下载完了,则退出循环(终止测速) 980 | break 981 | } else if contentLength == -1 { // 文件下载完成 且 文件大小未知,则退出循环(终止测速),例如:https://speed.cloudflare.com/__down?bytes=200000000 这样的,如果在 10 秒内就下载完成了,会导致测速结果明显偏低甚至显示为 0.00(下载速度太快时) 982 | break 983 | } 984 | // 获取上个时间片 985 | last_time_slice := timeStart.Add(timeSlice * time.Duration(timeCounter-1)) 986 | // 下载数据量 / (用当前时间 - 上个时间片/ 时间片) 987 | e.Add(float64(contentRead-lastContentRead) / (float64(currentTime.Sub(last_time_slice)) / float64(timeSlice))) 988 | } 989 | contentRead += int64(bufferRead) 990 | } 991 | speed := e.Value() / (dlTimeout.Seconds() / 120) / (1024 * 1024) 992 | // 输出结果 993 | // fmt.Printf("-IP %-15s 端口 %-5s 国家 %2s 延迟 %3s ms 速度 %2.1f MB/s%s\n", ip, strconv.Itoa(port), country, tcplatency, speed, strings.Repeat(" ", 12)) 994 | 995 | if *multipleNum == 1 || *speedTestThreads < 5 { 996 | // 输出结果 997 | if speed >= *speedLimit/2 { 998 | // 速度达到限速一半才在命令行输出 999 | fmt.Printf("-IP %-15s 端口 %-5s 国家 %2s 延迟 %3s ms 速度 %4.1f MB/s%s\n", ip, strconv.Itoa(port), country, tcplatency, speed, strings.Repeat(" ", 1)) 1000 | } 1001 | return speed 1002 | } else { 1003 | // 多协程测速会有速度损失,加以补偿 1004 | xspeed := speed * (*multipleNum) 1005 | fmt.Printf("-IP %-15s 端口 %-5s 延迟 %3s ms 速度 %4.1f MB/s, %.0f×%2.1f%s\n", ip, strconv.Itoa(port), tcplatency, xspeed, *multipleNum, speed, strings.Repeat(" ", 1)) 1006 | return xspeed 1007 | } 1008 | } 1009 | 1010 | // 并发下载测速函数 1011 | func downloadSpeedTest() { 1012 | var results []speedTestResult 1013 | var badresults []speedTestResult 1014 | 1015 | if *speedTestThreads > 0 { 1016 | fmt.Printf("\n\n开始下载测速\n") 1017 | if *speedTestThreads > 1 && *multipleNum == 1 { 1018 | fmt.Printf("\033[90m> 即将建立\033[0m \033[31m%d\033[0m \033[90m个并发协程,测速可能失真。建议增加\033[0m\033[4;33m %d \033[0m\033[90m倍补偿系数\033[0m\n", *speedTestThreads, *speedTestThreads) 1019 | 1020 | // 创建一个新的读数器 1021 | reader := bufio.NewReader(os.Stdin) 1022 | // for 循环检测用户输入 1023 | for { 1024 | // 获取用户输入 1025 | fmt.Print(" 请输入补偿系数(默认为1):") 1026 | input, err := reader.ReadString('\n') 1027 | if err != nil { 1028 | fmt.Println("\033[31m 无法读取输入:\033[0m", err) 1029 | os.Exit(1) 1030 | } 1031 | 1032 | // 去除输入字符串前后的空白字符 1033 | input = strings.TrimSpace(input) 1034 | 1035 | // 将用户输入转换为浮点数 1036 | if input != "" { 1037 | *multipleNum, err = strconv.ParseFloat(input, 64) 1038 | if err != nil { 1039 | fmt.Println("\033[31m 请输入一个有效的数字\033[0m") 1040 | *multipleNum = 1 // 补偿系数恢复初始值 1041 | continue 1042 | } 1043 | } 1044 | break 1045 | } 1046 | // 在这里使用multipleNum进行计算或其他操作 1047 | fmt.Printf("\n\033[90m> 并发测速补偿系数已设置为\033[0m \033[32m%.1f\033[0m\n\n", *multipleNum) 1048 | } 1049 | 1050 | // 根据ticp延迟排序tcp延迟结果 1051 | sort.Slice(totalResultChan, func(i, j int) bool { 1052 | durationI, _ := strconv.ParseFloat(totalResultChan[i].tcpDuration, 64) 1053 | durationJ, _ := strconv.ParseFloat(totalResultChan[j].tcpDuration, 64) 1054 | return durationI < durationJ 1055 | }) 1056 | 1057 | // 创建并发协程 1058 | var wg sync.WaitGroup 1059 | var countSt int32 = 0 // 下载测速进度计数器 1060 | var mu sync.Mutex // 同步锁 1061 | var resultCount int32 = 0 1062 | ctx, cancel := context.WithCancel(context.Background()) 1063 | // stopChan := make(chan struct{}) // 停止信号通道 1064 | netIDs := sync.Map{} // 使用 sync.Map 记录已存储结果的网络段 1065 | thread := make(chan struct{}, *speedTestThreads) 1066 | total := len(totalResultChan) 1067 | 1068 | for id, res := range totalResultChan { 1069 | select { 1070 | // case <-stopChan: 1071 | case <-ctx.Done(): 1072 | break 1073 | default: 1074 | wg.Add(1) 1075 | thread <- struct{}{} 1076 | go func(id int, res latencyTestResult) { 1077 | defer func() { 1078 | <-thread 1079 | wg.Done() 1080 | 1081 | select { 1082 | case <-ctx.Done(): 1083 | countStInt := int(atomic.LoadInt32(&countSt)) 1084 | logWithProgress(countStInt, total) 1085 | fmt.Printf(" 优选 IP:\033[1;32;5m%d\033[0m/\033[32m%d\033[0m\r", atomic.LoadInt32(&resultCount), *RequestNum) 1086 | return 1087 | default: 1088 | // 记录下载测速进度 1089 | atomic.AddInt32(&countSt, 1) 1090 | 1091 | // 输出测速进度 1092 | countStInt := int(atomic.LoadInt32(&countSt)) 1093 | logWithProgress(countStInt, total) 1094 | fmt.Printf(" 优选 IP:\033[1;32;5m%d\033[0m/\033[32m%d\033[0m\r", atomic.LoadInt32(&resultCount), *RequestNum) 1095 | } 1096 | }() 1097 | 1098 | select { 1099 | case <-ctx.Done(): 1100 | return 1101 | default: 1102 | var downloadSpeed float64 1103 | // 获取ip的前三段即网络段net-id 1104 | netID := getNetworkSegment(res.ip) 1105 | 1106 | // 程序提前结束条件判断 1107 | if atomic.LoadInt32(&resultCount) >= int32(*RequestNum) && countAlive >= 40 && !*DownloadTestAll { 1108 | // close(stopChan) // 关闭停止信号通道,通知所有协程停止 1109 | cancel() 1110 | fmt.Println("\n\033[32m已获取足够数量满足条件的ip,提前结束测试!\033[0m") 1111 | return 1112 | } 1113 | // 每个ip网段仅保留一个主机,当有合格ip后不再对相同网段测速 1114 | if _, exists := netIDs.Load(netID); !exists { 1115 | downloadSpeed = getDownloadSpeed(res.ip, res.port, res.tls, res.latency, res.tcpDuration, res.country) 1116 | } 1117 | 1118 | mu.Lock() // 同步锁 1119 | defer mu.Unlock() 1120 | if downloadSpeed >= *speedLimit { 1121 | // 如果当前网络段不存在于networkSegments中,一个ip端仅收录一个达标ip 1122 | if _, exists := netIDs.Load(netID); !exists { 1123 | // 将当前网络段标记为已存储 1124 | netIDs.Store(netID, true) 1125 | 1126 | if atomic.LoadInt32(&resultCount) < int32(*RequestNum) && !*DownloadTestAll { 1127 | // 将当前测速结果添加到results中 1128 | results = append(results, speedTestResult{latencyTestResult: res, downloadSpeed: downloadSpeed}) 1129 | // 如果下载速度大于等于速度限制,增加resultCount计数 1130 | atomic.AddInt32(&resultCount, 1) 1131 | if atomic.LoadInt32(&resultCount) >= int32(*RequestNum) && !*DownloadTestAll { 1132 | cancel() 1133 | } 1134 | } else if *DownloadTestAll { 1135 | // 将当前测速结果添加到results中 1136 | results = append(results, speedTestResult{latencyTestResult: res, downloadSpeed: downloadSpeed}) 1137 | // 如果下载速度大于等于速度限制,增加resultCount计数 1138 | atomic.AddInt32(&resultCount, 1) 1139 | } 1140 | } 1141 | } else { 1142 | badresults = append(badresults, speedTestResult{latencyTestResult: res, downloadSpeed: downloadSpeed}) 1143 | } 1144 | } 1145 | }(id, res) 1146 | } 1147 | } 1148 | wg.Wait() 1149 | // 测速进程运行完成 1150 | // time.Sleep(10 * time.Second) 1151 | // fmt.Println("\n下载速度测试完成!") 1152 | // pause() 1153 | } else { 1154 | for _, res := range totalResultChan { 1155 | results = append(results, speedTestResult{latencyTestResult: res}) 1156 | } 1157 | } 1158 | if *speedTestThreads > 0 { 1159 | sort.Slice(results, func(i, j int) bool { 1160 | return results[i].downloadSpeed > results[j].downloadSpeed 1161 | }) 1162 | sort.Slice(badresults, func(i, j int) bool { 1163 | return badresults[i].downloadSpeed > badresults[j].downloadSpeed 1164 | }) 1165 | } else { 1166 | sort.Slice(results, func(i, j int) bool { 1167 | return results[i].latencyTestResult.tcpDuration < results[j].latencyTestResult.tcpDuration 1168 | }) 1169 | } 1170 | 1171 | // 下载测速结果写入文件 1172 | writeResults(results, badresults) 1173 | } 1174 | 1175 | // 辅助函数,提取网络段 1176 | func getNetworkSegment(ip string) string { 1177 | parts := strings.Split(ip, ".") 1178 | if len(parts) < 3 { 1179 | return ip 1180 | } 1181 | return strings.Join(parts[:3], ".") 1182 | } 1183 | 1184 | // writeResults 写入文件函数 1185 | func writeResults(results []speedTestResult, badresults []speedTestResult) { 1186 | countQualified = len(results) 1187 | countUnQualified := len(badresults) 1188 | 1189 | if countQualified+countUnQualified == 0 { 1190 | os.Exit(0) 1191 | } 1192 | 1193 | if countQualified > 0 { 1194 | handleQualifiedResults(results) 1195 | } else if countUnQualified > 0 { 1196 | fmt.Printf("\n\n未发现下载速度高于 \033[31m%.1f\033[0m MB/s的IP,但存在可用低速IP\n", *speedLimit) 1197 | } 1198 | 1199 | if countUnQualified > 0 { 1200 | handleUnqualifiedResults(badresults) 1201 | } 1202 | } 1203 | 1204 | // 合格ip结果写入文件 1205 | func handleQualifiedResults(results []speedTestResult) { 1206 | file, err := os.Create(*outFile) 1207 | if err != nil { 1208 | fmt.Printf("无法创建文件: %v\n", err) 1209 | return 1210 | } 1211 | defer file.Close() 1212 | file.WriteString("\xEF\xBB\xBF") // 标记为utf-8 bom编码,防止excel打开中文乱码 1213 | 1214 | writer := csv.NewWriter(file) 1215 | defer writer.Flush() 1216 | 1217 | if *speedTestThreads > 0 { 1218 | writer.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "http延迟", "tcp延迟", "下载速度(MB/s)"}) 1219 | } else { 1220 | writer.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "延迟(ms)"}) 1221 | } 1222 | 1223 | fmt.Println("\n\n\033[90m正在查询优选ip的ASN信息并处理,请稍等!\033[0m") 1224 | 1225 | // 创建并发协程 1226 | var wg sync.WaitGroup 1227 | type resultWithIPTag struct { 1228 | result speedTestResult 1229 | ipTag string 1230 | } 1231 | type resultWithIPTagByIndex struct { 1232 | index int 1233 | resultWithIPTag 1234 | } 1235 | requestNum := *RequestNum 1236 | index := 0 1237 | resultsChan := make(chan resultWithIPTagByIndex) 1238 | 1239 | // 判断 apiKey 是否有效 1240 | if *IPInfoApiKey != "" { 1241 | apiKeys := *IPInfoApiKey 1242 | apiKeysArray := strings.Split(apiKeys, ",") 1243 | for _, key := range apiKeysArray { 1244 | if task.IsApiKeyNotExceed(key) { 1245 | apiKey = key 1246 | break 1247 | } 1248 | } 1249 | } 1250 | 1251 | for _, res := range results { 1252 | if *speedTestThreads > 0 && res.downloadSpeed >= float64(*speedLimit) && index < requestNum { 1253 | index++ 1254 | wg.Add(1) 1255 | go func(index int, res speedTestResult) { 1256 | defer func() { 1257 | wg.Done() 1258 | fmt.Print(".") 1259 | }() 1260 | OutFileName := strings.Split(*outFile, ".")[0] 1261 | 1262 | // 如果 -outFile 参数错误设置 1263 | var suffixName string 1264 | if strings.Contains(OutFileName, "_") { 1265 | suffixName = strings.Split(OutFileName, "_")[1] 1266 | } else { 1267 | suffixName = OutFileName 1268 | } 1269 | 1270 | dataCenterCountry := res.country 1271 | ipTag := getIPTag(res.ip, res.port, dataCenterCountry, suffixName) 1272 | if ipTag == "" { 1273 | ipTag = res.country 1274 | } 1275 | // 添加索引以便不打乱顺序 1276 | resultsChan <- resultWithIPTagByIndex{index - 1, resultWithIPTag{res, ipTag}} 1277 | }(index, res) 1278 | } else { 1279 | writer.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency, res.tcpDuration, fmt.Sprintf("%.1f", res.downloadSpeed)}) 1280 | } 1281 | } 1282 | 1283 | go func() { 1284 | wg.Wait() 1285 | close(resultsChan) 1286 | }() 1287 | 1288 | resultSlice := make([]resultWithIPTag, requestNum) 1289 | for result := range resultsChan { 1290 | resultSlice[result.index] = result.resultWithIPTag 1291 | } 1292 | fmt.Printf("\n优选ip下载速度高于 \033[32m%2.1f\033[0m MB/s,测速结果:\n", *speedLimit) 1293 | for _, result := range resultSlice { 1294 | if result.result.ip != "" { 1295 | writer.Write([]string{result.result.ip, strconv.Itoa(result.result.port), strconv.FormatBool(*enableTLS), result.result.dataCenter, result.result.region, result.ipTag, result.result.city, result.result.latency, result.result.tcpDuration, fmt.Sprintf("%.1f", result.result.downloadSpeed)}) 1296 | fmt.Printf("%-15s:%-5d#%-2s-%-4.1f MB/s 平均延迟 %-3sms\n", result.result.ip, result.result.port, result.result.country, result.result.downloadSpeed, result.result.tcpDuration) 1297 | } 1298 | } 1299 | 1300 | fmt.Printf("\n\033[32m>\033[0m 优质ip写入 \033[90;4m%s\033[0m 耗时 %d 秒\n", *outFile, time.Since(startTime)/time.Second) 1301 | } 1302 | 1303 | // 未达标结果,保存以便查阅 1304 | func handleUnqualifiedResults(badresults []speedTestResult) { 1305 | outFileUnqualified := "result_Unqualified.csv" 1306 | fileUnqualified, err := os.Create(outFileUnqualified) 1307 | if err != nil { 1308 | fmt.Printf("无法创建文件: %v\n", err) 1309 | return 1310 | } 1311 | defer fileUnqualified.Close() 1312 | fileUnqualified.WriteString("\xEF\xBB\xBF") // 标记为utf-8 bom编码,防止excel打开中文乱码 1313 | 1314 | writerUnqualified := csv.NewWriter(fileUnqualified) 1315 | defer writerUnqualified.Flush() 1316 | 1317 | if *speedTestThreads > 0 { 1318 | writerUnqualified.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "http延迟", "tcp延迟", "下载速度(MB/s)"}) 1319 | } else { 1320 | writerUnqualified.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "延迟(ms)"}) 1321 | } 1322 | 1323 | for _, res := range badresults { 1324 | if *speedTestThreads > 0 { 1325 | writerUnqualified.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency, res.tcpDuration, fmt.Sprintf("%.1f", res.downloadSpeed)}) 1326 | } else { 1327 | writerUnqualified.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency}) 1328 | } 1329 | } 1330 | 1331 | fmt.Printf("\033[31m>\033[0m 低速ip写入 \033[4;90m%s\033[0m 耗时 %d 秒\n", outFileUnqualified, time.Since(startTime)/time.Second) 1332 | } 1333 | 1334 | // 忽略大小写的对比函数 1335 | func containsIgnoreCase(slice []string, item string) bool { 1336 | item = strings.ToUpper(item) 1337 | for _, s := range slice { 1338 | if s == item { 1339 | return true 1340 | } 1341 | } 1342 | return false 1343 | } 1344 | 1345 | // tcping tcp延迟检测函数 1346 | func tcping(ip string, port int) (bool, time.Duration) { 1347 | startTime := time.Now() 1348 | 1349 | conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, strconv.Itoa(port)), tcpTimeout) 1350 | if err != nil { 1351 | return false, 0 1352 | } 1353 | defer conn.Close() 1354 | duration := time.Since(startTime) 1355 | return true, duration 1356 | } 1357 | 1358 | func getIPTag(ip string, port int, dataCenterCoCo string, tag string) string { 1359 | // 通过api接口获取ip信息 1360 | info, err := task.GetIPInfo(ip, apiKey) 1361 | if err != nil { 1362 | fmt.Println("Error:", err) 1363 | return "" 1364 | } 1365 | 1366 | // 判断原生ip或广播ip 1367 | var uniIPStatus string 1368 | if info.IsUniIP() { 1369 | uniIPStatus = "Uni" 1370 | } else { 1371 | uniIPStatus = "Bro" 1372 | } 1373 | 1374 | // 获取ASN组织者名称缩写 1375 | org := info.GetOrgNameAbbr() 1376 | 1377 | // 获取ip类型,住宅、商务、机房等 1378 | ipType, err := info.GetIPType() 1379 | if err != nil { 1380 | fmt.Println("Error:", err) 1381 | return "" 1382 | } 1383 | 1384 | // 根据数据中心地址和ip位置是否相同,设置显示信息 1385 | var ipLocation string 1386 | var cnProxy string 1387 | ipCoCo := info.Location.Country_code 1388 | if ipCoCo == dataCenterCoCo { 1389 | ipLocation = ipCoCo 1390 | } else { 1391 | ipLocation = ipCoCo + "-" + dataCenterCoCo 1392 | if ipCoCo == "CN" { 1393 | cnProxy = "中转" 1394 | } 1395 | } 1396 | 1397 | // 格式化输出内容 1398 | // line := fmt.Sprintf("%s:%d#%s-%s%s-%s-%d丨%s", ip, port, ipLocation, uniIPStatus, ipType, org, info.ASN.ASN, tag) 1399 | // fmt.Println(line) 1400 | 1401 | ipTag := fmt.Sprintf("%s-%s%s%s-%s-%d丨%s", ipLocation, uniIPStatus, ipType, cnProxy, org, info.ASN.ASN, tag) 1402 | return ipTag 1403 | } 1404 | 1405 | // main 主程序 1406 | func main() { 1407 | flag.Parse() // 解析命令行参数 1408 | startTime = time.Now() 1409 | 1410 | osType := runtime.GOOS 1411 | if osType == "linux" { 1412 | increaseMaxOpenFiles() 1413 | } 1414 | 1415 | // 下载locations.json 1416 | locationsJsonDownload() 1417 | 1418 | // 如果不检测能访问chatgpt的proxyip,则执行网络检查 1419 | // 检测proxyip必须可以访问外网 1420 | if !*CheckGPT { 1421 | // 网络环境检测,如网络不正常自动退出 1422 | if !autoNetworkDetection() { 1423 | return 1424 | } 1425 | } 1426 | 1427 | // 存储国家代码到数组中 1428 | if *countryCodes != "" { 1429 | countries = strings.Split(strings.ToUpper(*countryCodes), ",") 1430 | } 1431 | 1432 | // 存储排除代码到数组中 1433 | if *ExcludedCounties != "" { 1434 | exceludedCountries = strings.Split(strings.ToUpper(*ExcludedCounties), ",") 1435 | } 1436 | 1437 | // ipLib列表文件下载及时效性检测 1438 | if *DownloadipLib { 1439 | checked := make(map[string]bool) 1440 | for file, url := range ipLibs { 1441 | if url == "https://zip.baipiao.eu.org/" { 1442 | if !checked[url] { 1443 | fmt.Printf("\033[90m检查 %s\033[0m\n", file) 1444 | checkIPLib(url, file) 1445 | checked[url] = true 1446 | } 1447 | } else { 1448 | fmt.Printf("\033[90m检查 %s\033[0m\n", file) 1449 | checkIPLib(url, file) 1450 | } 1451 | } 1452 | fmt.Printf("\033[32mIP库文件已处于最新状态,请修改\033[0m \033[90m-iplib=false\033[0m \033[32m重新运行程序\033[0m\n") 1453 | os.Exit(0) 1454 | } else { 1455 | lowerFile := strings.ToLower(*File) 1456 | var matchedFile string 1457 | for key := range ipLibs { 1458 | if strings.ToLower(key) == lowerFile { 1459 | matchedFile = key 1460 | break 1461 | } 1462 | } 1463 | if matchedFile != "" { 1464 | *File = matchedFile 1465 | checkIPLib(ipLibs[matchedFile], *File) 1466 | } else if *File == "ip.txt" { 1467 | _, err := os.Stat(*File) 1468 | if os.IsNotExist(err) { 1469 | fmt.Printf("%s 不存在,切换网络IP库 >>>\n", *File) 1470 | *File = "txt.zip" 1471 | ipLiburl := "https://zip.baipiao.eu.org/" 1472 | checkIPLib(ipLiburl, *File) 1473 | } 1474 | } else { 1475 | _, err := os.Stat(*File) 1476 | if os.IsNotExist(err) { 1477 | fmt.Printf("%s 文件不存在,请检查输入!\n", *File) 1478 | os.Exit(0) 1479 | } 1480 | } 1481 | } 1482 | 1483 | // 支持直接设置ip参数检测单个ip 1484 | if *DirectIP != "" { 1485 | *outFile = "result_TestIP.csv" 1486 | var DirectIPs []string 1487 | DirectIPs = strings.Split(*DirectIP, ",") 1488 | 1489 | totalIPs = len(DirectIPs) 1490 | for _, ipPort := range DirectIPs { 1491 | if len(strings.Split(ipPort, ":")) == 2 { 1492 | port, _ := strconv.Atoi(strings.Split(ipPort, ":")[1]) 1493 | ipPort := strings.Fields(ipPort) 1494 | delayedDetectionIPs(ipPort, *enableTLS, port) 1495 | } else { 1496 | port := *defaultPort 1497 | ips := strings.Fields(ipPort) 1498 | delayedDetectionIPs(ips, *enableTLS, port) 1499 | } 1500 | } 1501 | 1502 | pause() 1503 | if *CheckGPT { 1504 | checkGptProxy() 1505 | } 1506 | downloadSpeedTest() 1507 | os.Exit(0) 1508 | } else { 1509 | // 设置测速结果文件名 1510 | resetOutFileName(*File) 1511 | } 1512 | 1513 | if strings.HasSuffix(*File, ".zip") { 1514 | // 获取压缩包文件名 1515 | ZipedFileName := strings.Split(*File, ".")[0] 1516 | caser := cases.Title(language.English) // 使用English作为默认语言标签 1517 | ZipedFileName = caser.String(ZipedFileName) // 字母小写 1518 | 1519 | // 生成解压文件文件名 1520 | UnZipedFile := "ip_" + ZipedFileName + "_unZiped.txt" 1521 | 1522 | fileInfos, err := task.UnZip2txtFile(*File, UnZipedFile) 1523 | if err != nil { 1524 | fmt.Printf("解压文件时出错: %v\n", err) 1525 | return 1526 | } 1527 | 1528 | if fileInfos != nil { 1529 | // ASN 格式 1530 | processASNZipedFiles(fileInfos) 1531 | if *CheckGPT { 1532 | checkGptProxy() 1533 | } 1534 | // 下载测速并保存结果 1535 | downloadSpeedTest() 1536 | } else { 1537 | // 非 ASN 格式,使用合并后的文件 1538 | task.UnZip2txtFile(*File, UnZipedFile) 1539 | processIPListFile(UnZipedFile) 1540 | if *CheckGPT { 1541 | checkGptProxy() 1542 | } 1543 | // 下载测速并保存结果 1544 | downloadSpeedTest() 1545 | } 1546 | } else { 1547 | 1548 | if strings.ToLower(*File) == "ip_cfv4ipdb.txt" { 1549 | parsedFile := "ip_CFv4IPDB_Parsed.txt" 1550 | task.RandomParseCIDR(*File, parsedFile) 1551 | *File = parsedFile 1552 | // 设置测速结果文件名 1553 | resetOutFileName(*File) 1554 | *outFile = "result_Cfv4ipdb.csv" 1555 | } 1556 | if strings.Contains(strings.ToLower(*File), "cidr") { 1557 | pasedName := strings.Split(*File, ".")[0] 1558 | parsedFile := pasedName + "_Pased.txt" 1559 | task.RandomParseCIDR(*File, parsedFile) 1560 | *File = parsedFile 1561 | // 设置测速结果文件名 1562 | resetOutFileName(*File) 1563 | *outFile = "result_" + strings.Split(pasedName, "_")[1] + ".csv" 1564 | } 1565 | 1566 | // 处理非 ZIP 文件 1567 | processIPListFile(*File) 1568 | if *CheckGPT { 1569 | checkGptProxy() 1570 | } 1571 | // 下载测速并保存结果 1572 | downloadSpeedTest() 1573 | } 1574 | 1575 | // 更新数据 1576 | reader := bufio.NewReader(os.Stdin) 1577 | switch { 1578 | case *Domain != "" && *Token != "" && countQualified > 0: 1579 | switch strings.ToLower(*outFile) { 1580 | case "result_baipiaoge.csv", "result_proxyipdb.csv", "result_cfv4ipdb.csv", "result_scanner.csv", "result_selected.csv", "result_fofa.csv", "result_fofas.csv", "result_bihaicfip.csv", "result_bihaicloudcfip.csv": 1581 | fmt.Printf("\n> 优质ip数量:\033[32m%d\033[0m ,是否要上传数据?(y/n):", countQualified) 1582 | // 创建一个通道用于接收用户输入 1583 | inputChan := make(chan string, 1) 1584 | 1585 | // 启动一个 goroutine 来获取用户输入 1586 | go func() { 1587 | var input string 1588 | fmt.Scanln(&input) 1589 | input = strings.TrimSpace(input) 1590 | inputChan <- input 1591 | }() 1592 | 1593 | // 等待用户输入或超时 1594 | select { 1595 | case input := <-inputChan: 1596 | if input == "y" { 1597 | task.DataUpdate(*outFile, *Domain, *Token, baseLens, countQualified) 1598 | } else if input == "n" { 1599 | fmt.Println("退出程序") 1600 | } else { 1601 | fmt.Println("退出程序") 1602 | } 1603 | case <-time.After(5 * time.Second): 1604 | task.DataUpdate(*outFile, *Domain, *Token, baseLens, countQualified) 1605 | } 1606 | 1607 | default: 1608 | fmt.Printf("\n> 优质ip数量:\033[32m%d\033[0m ,是否要上传数据?(y/n):", countQualified) 1609 | input, _ := reader.ReadString('\n') 1610 | input = strings.TrimSpace(input) 1611 | if input == "y" { 1612 | task.DataUpdate(*outFile, *Domain, *Token, baseLens, countQualified) 1613 | } else { 1614 | fmt.Println("退出程序") 1615 | } 1616 | } 1617 | case (*Domain == "" || *Token == "") && countQualified > 0: 1618 | fmt.Printf("\n> 优质ip数量:\033[32m%d\033[0m ,是否要上传数据?(y/n):", countQualified) 1619 | input, _ := reader.ReadString('\n') 1620 | input = strings.TrimSpace(input) 1621 | if input == "y" { 1622 | if *Domain == "" { 1623 | fmt.Println("\033[90m请输入Domain(网址):\033[0m") 1624 | domain, _ := reader.ReadString('\n') 1625 | *Domain = strings.TrimSpace(domain) 1626 | } 1627 | if *Token == "" { 1628 | fmt.Println("\033[90m请输入Token:\033[0m") 1629 | token, _ := reader.ReadString('\n') 1630 | *Token = strings.TrimSpace(token) 1631 | } 1632 | if *Domain != "" && *Token != "" { 1633 | task.DataUpdate(*outFile, *Domain, *Token, baseLens, countQualified) 1634 | } else { 1635 | fmt.Println("\033[31m主机名或token缺失,本次更新取消!\033[0m") 1636 | os.Exit(0) 1637 | } 1638 | 1639 | } else { 1640 | fmt.Println("退出程序") 1641 | } 1642 | default: 1643 | os.Exit(0) 1644 | } 1645 | } 1646 | -------------------------------------------------------------------------------- /task/.gitignore: -------------------------------------------------------------------------------- 1 | # 项目和插件的二进制文件 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.ini 8 | *.yaml 9 | 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | *.log 17 | *.csv 18 | *.rar 19 | *.txt 20 | *.zip 21 | *.bat 22 | locations.json 23 | 24 | 25 | # Dependency directories (remove the comment below to include it) 26 | vendor/ 27 | pkd/ 28 | 29 | # Go workspace file 30 | go.work 31 | 32 | # 编辑器文件夹 33 | .idea/ 34 | .vscode/ 35 | */.vscode/ 36 | */.idea/ 37 | build/ 38 | bin/ 39 | temp/ 40 | test/ 41 | ipLAB/ -------------------------------------------------------------------------------- /task/getASN.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // IPInfo 用于存储从API返回的IP信息 13 | type IPInfo struct { 14 | IP string `json:"ip"` 15 | Company CompanyInfo `json:"company"` 16 | ASN ASNInfo `json:"asn"` 17 | Location LocationInfo `json:"location"` 18 | } 19 | 20 | // CompanyInfo 用于存储公司信息 21 | type CompanyInfo struct { 22 | Name string `json:"name"` 23 | Type string `json:"type"` 24 | } 25 | 26 | // ASNInfo 用于存储ASN信息 27 | type ASNInfo struct { 28 | ASN int `json:"asn"` 29 | Org string `json:"org"` 30 | Country string `json:"country"` 31 | Type string `json:"type"` 32 | } 33 | 34 | // LocationInfo 用于存储位置信息 35 | type LocationInfo struct { 36 | Country_code string `json:"country_code"` 37 | } 38 | 39 | // getIPInfo 查询IP信息并返回其类型(住宅、商务、专线或其他) 40 | func GetIPInfo(ip, apiKey string) (IPInfo, error) { 41 | var url string 42 | if apiKey != "" { 43 | url = fmt.Sprintf("https://api.ipapi.is?q=%s&key=%s", ip, apiKey) 44 | } else { 45 | fmt.Println("使用免费接口,如检测量大请自行提供apiKey\r") 46 | url = fmt.Sprintf("https://api.ipapi.is/?ip=%s", ip) 47 | } 48 | 49 | // 创建一个自定义的 http.Client,并设置超时时间 50 | client := http.Client{ 51 | Timeout: 50 * time.Second, // 设置超时时间为10秒 52 | } 53 | 54 | // 使用自定义的 client 发送 GET 请求 55 | resp, err := client.Get(url) 56 | if err != nil { 57 | return IPInfo{}, err 58 | } 59 | defer resp.Body.Close() 60 | 61 | body, err := io.ReadAll(resp.Body) 62 | if err != nil { 63 | return IPInfo{}, err 64 | } 65 | 66 | var ipInfo IPInfo 67 | err = json.Unmarshal(body, &ipInfo) 68 | if err != nil { 69 | return IPInfo{}, err 70 | } 71 | return ipInfo, err 72 | } 73 | 74 | // 为 IPInfo 结构体创建 getIPType 方法 75 | func (info *IPInfo) GetIPType() (string, error) { 76 | if info.Company.Type == "isp" { 77 | switch info.ASN.Type { 78 | case "isp": 79 | return "住宅", nil 80 | case "business": 81 | return "家宽", nil 82 | case "hosting": 83 | return "托管", nil 84 | default: 85 | return info.ASN.Type, nil 86 | } 87 | } else if info.Company.Type == "business" { 88 | switch info.ASN.Type { 89 | case "isp": 90 | return "商宽", nil 91 | case "business": 92 | return "商务", nil 93 | case "hosting": 94 | return "VPS", nil 95 | default: 96 | return info.ASN.Type, nil 97 | } 98 | } else if info.Company.Type == "hosting" { 99 | switch info.ASN.Type { 100 | case "isp": 101 | return "中转", nil 102 | case "business": 103 | return "商管", nil 104 | case "hosting": 105 | return "机房", nil 106 | default: 107 | return "行业机房", nil 108 | } 109 | } else { 110 | return info.ASN.Type, nil 111 | } 112 | } 113 | 114 | // 原生ip还是广播ip 115 | func (info *IPInfo) IsUniIP() bool { 116 | asnCountry := strings.ToUpper(info.ASN.Country) 117 | locCountry := strings.ToUpper(info.Location.Country_code) 118 | if asnCountry == locCountry { 119 | return true 120 | } 121 | return false 122 | } 123 | 124 | // Asn组织名称缩写 125 | func (info *IPInfo) GetOrgNameAbbr() string { 126 | mappings := map[string]string{ 127 | "sk broadband": "SKB", 128 | "cmb": "CMB", 129 | "taegu": "CMB", 130 | "spectrum": "CFS", 131 | "cloudflare": "CF", 132 | "bigcommerce": "BigC", 133 | "tcloudnet": "TCN", 134 | "amazon": "AWS", 135 | "linked": "Lin", 136 | "porsche": "Porsche", 137 | "tencent": "Tencent", 138 | "alibaba": "ALi", 139 | "oracle": "Oracle", 140 | "powercomm": "LG", 141 | "powervis": "LG", 142 | "zdm network": "ZDM", 143 | "cogent": "Cog", 144 | "kirino": "Kirino", 145 | "microsoft": "Microsoft", 146 | "it7": "IT7", 147 | "cluster": "Cluster", 148 | "m247": "M247", 149 | "multacom": "MUL", 150 | "dimt": "DMIT", 151 | "chunghwa": "CHT", 152 | "pittqiao": "PIQ", 153 | } 154 | org := info.ASN.Org 155 | for key, value := range mappings { 156 | if strings.Contains(strings.ToLower(org), key) { 157 | return value 158 | } 159 | } 160 | 161 | if len(org) > 5 { 162 | return strings.ToUpper(org[:3]) 163 | } 164 | return strings.ToUpper(org) 165 | } 166 | 167 | // 检测当前apiKey是否达到上限 168 | func IsApiKeyNotExceed(apiKey string) bool { 169 | url := fmt.Sprintf("https://api.ipapi.is?q=%s&key=%s", "8.8.8.8", apiKey) 170 | 171 | // 创建一个自定义的 http.Client,并设置超时时间 172 | client := http.Client{ 173 | Timeout: 50 * time.Second, // 设置超时时间为10秒 174 | } 175 | 176 | // 使用自定义的 client 发送 GET 请求 177 | resp, err := client.Get(url) 178 | if err != nil { 179 | fmt.Printf("连接错误:%v\n", err) 180 | return false 181 | } 182 | defer resp.Body.Close() 183 | 184 | body, err := io.ReadAll(resp.Body) 185 | if err != nil { 186 | fmt.Print("无法读取") 187 | return false 188 | } 189 | // fmt.Println(string(body)) 190 | if strings.Contains(string(body), "exceeded") { 191 | fmt.Println("ip-API达到限额") 192 | return false 193 | } 194 | return true 195 | } 196 | -------------------------------------------------------------------------------- /task/gptTest.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/http" 11 | // "regexp" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const ( 17 | timeout = 50 * time.Second // 连接超时时间 18 | maxDuration = 50 * time.Second // 请求和读取响应的最大持续时间 19 | // requestURL = "https://speed.cloudflare.com/cdn-cgi/trace" // 请求trace URL 20 | // requestURL = "https://chatgpt.com/cdn-cgi/trace/" // 请求trace URL 21 | requestURL = "https://chatgpt.com" // 请求trace URL 22 | ) 23 | 24 | // checkIPPort 尝试连接到给定的IP和端口,并发送HTTP请求以获取响应内容 25 | func CheckGptProxy(ip, port string) bool { 26 | 27 | // 设置连接超时的拨号器 28 | dialer := &net.Dialer{ 29 | Timeout: timeout, 30 | KeepAlive: 0, 31 | } 32 | 33 | // 创建一个带有超时的连接 34 | conn, err := dialer.DialContext(context.Background(), "tcp", net.JoinHostPort(ip, port)) 35 | if err != nil { 36 | // fmt.Printf("建立TCP连接错误 %s:%s - %s\n", ip, port, err) 37 | return false 38 | } 39 | defer conn.Close() 40 | 41 | // 设置HTTP客户端,使用自定义的拨号器 42 | client := http.Client{ 43 | Transport: &http.Transport{ 44 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 45 | return conn, nil 46 | }, 47 | // 为HTTPS设置TLS配置 48 | TLSClientConfig: &tls.Config{ 49 | InsecureSkipVerify: true, // 如果需要验证证书,请删除此行 50 | }, 51 | }, 52 | Timeout: maxDuration, 53 | } 54 | 55 | // 创建新的HTTP GET请求 56 | req, _ := http.NewRequest("GET", requestURL, nil) 57 | 58 | // 设置请求头中的用户代理 59 | req.Header.Set("User-Agent", "Mozilla/5.0") 60 | req.Close = true 61 | 62 | // 发送请求并获取响应 63 | resp, err := client.Do(req) 64 | if err != nil { 65 | // fmt.Printf("访问错误 %s - %s\n", net.JoinHostPort(ip, port), err) 66 | return false 67 | } 68 | defer resp.Body.Close() 69 | 70 | // 将响应体内容复制到缓冲区 71 | var buf bytes.Buffer 72 | _, err = io.Copy(&buf, resp.Body) 73 | if err != nil { 74 | // fmt.Printf("读取响应体错误: %v\n", err) 75 | return false 76 | } 77 | 78 | content := buf.String() 79 | // fmt.Printf("来自 %s 的响应内容:\n%s\n", net.JoinHostPort(ip, port), content) 80 | // if strings.Contains(content, "html") { 81 | // fmt.Printf("来自 %s 的请求,成功\n", net.JoinHostPort(ip, port)) 82 | // } 83 | // fmt.Println(resp.Header) 84 | // fmt.Println(resp.StatusCode) 85 | fmt.Println(resp.Header.Get("Cf-Mitigated")) 86 | 87 | if strings.Contains(content, "blocked") { 88 | fmt.Println("已被封锁") 89 | return false 90 | } else { 91 | fmt.Println("网页端正常") 92 | return true 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /task/linux.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package task 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | ) 10 | 11 | func CheckProxyEnabled() bool { 12 | fmt.Println("如果使用WSL建议手动检查是否开启代理") 13 | return os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" 14 | } -------------------------------------------------------------------------------- /task/processCIDR.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "bufio" // 用于读取文件 5 | // "flag" 6 | "fmt" 7 | 8 | // "fmt" 9 | "log" // 用于日志记录 10 | "math/rand" // 用于生成随机数 11 | "net" // 用于IP地址和网络相关操作 12 | "os" // 用于文件操作 13 | "strings" // 用于字符串操作 14 | "time" // 用于时间相关操作 15 | ) 16 | 17 | const ( 18 | defaultInputFile = "ip_CFv4IPDB.txt" 19 | defaultOutputFile = "ip_CFv4IPDB_Parsed.txt" 20 | ) 21 | 22 | var ( 23 | // TestAll 表示是否测试所有IP 24 | TestAll = false 25 | // IPFile 是包含IP范围的文件名 26 | IPFile string 27 | IPText string 28 | ) 29 | 30 | // InitRandSeed 初始化随机数种子 31 | func InitRandSeed() { 32 | rand.Seed(time.Now().UnixNano()) 33 | } 34 | 35 | // isIPv4 检查给定的IP是否为IPv4 36 | func isIPv4(ip string) bool { 37 | return strings.Contains(ip, ".") 38 | } 39 | 40 | // randIPEndWith 生成一个在给定范围内的随机字节值 41 | func randIPEndWith(num byte) byte { 42 | if num == 0 { // 对于 /32 或 /128 这种单独的IP 43 | return byte(0) 44 | } 45 | return byte(rand.Intn(int(num))) 46 | } 47 | 48 | // IPRanges 表示一组IP地址范围的结构体 49 | type IPRanges struct { 50 | ips []*net.IPAddr // 存储生成的IP地址 51 | mask string // 子网掩码 52 | firstIP net.IP // 起始IP地址 53 | ipNet *net.IPNet // IP网络 54 | } 55 | 56 | // newIPRanges 创建一个新的IPRanges实例 57 | func newIPRanges() *IPRanges { 58 | return &IPRanges{ 59 | ips: make([]*net.IPAddr, 0), 60 | } 61 | } 62 | 63 | // fixIP 如果是单独的IP,则添加子网掩码 64 | func (r *IPRanges) fixIP(ip string) string { 65 | if i := strings.IndexByte(ip, '/'); i < 0 { 66 | if isIPv4(ip) { 67 | r.mask = "/32" 68 | } else { 69 | r.mask = "/128" 70 | } 71 | ip += r.mask 72 | } else { 73 | r.mask = ip[i:] 74 | } 75 | return ip 76 | } 77 | 78 | // parseCIDR 解析CIDR格式的IP范围 79 | func (r *IPRanges) parseCIDR(ip string) { 80 | var err error 81 | if r.firstIP, r.ipNet, err = net.ParseCIDR(r.fixIP(ip)); err != nil { 82 | log.Fatalln("ParseCIDR error:", err) 83 | } 84 | } 85 | 86 | // appendIPv4 将IPv4地址添加到列表中 87 | func (r *IPRanges) appendIPv4(d byte) { 88 | r.appendIP(net.IPv4(r.firstIP[12], r.firstIP[13], r.firstIP[14], d)) 89 | } 90 | 91 | // appendIP 将IP地址添加到列表中 92 | func (r *IPRanges) appendIP(ip net.IP) { 93 | r.ips = append(r.ips, &net.IPAddr{IP: ip}) 94 | } 95 | 96 | // getIPRange 返回第四段IP的最小值及可用数目 97 | func (r *IPRanges) getIPRange() (minIP, hosts byte) { 98 | minIP = r.firstIP[15] & r.ipNet.Mask[3] // IP第四段最小值 99 | 100 | // 计算主机数量 101 | m := net.IPv4Mask(255, 255, 255, 255) 102 | for i, v := range r.ipNet.Mask { 103 | m[i] ^= v 104 | } 105 | total := int(m[3]) + 1 // 总可用IP数 106 | if total > 255 { // 矫正第四段可用IP数 107 | hosts = 255 108 | return 109 | } 110 | hosts = byte(total) 111 | return 112 | } 113 | 114 | // chooseIPv4 生成CIDR范围内的随机IPv4地址 115 | func (r *IPRanges) chooseIPv4() { 116 | if r.mask == "/32" { // 单个IP则无需随机,直接加入自身即可 117 | r.appendIP(r.firstIP) 118 | } else { 119 | minIP, hosts := r.getIPRange() // 返回第四段IP的最小值及可用数目 120 | for r.ipNet.Contains(r.firstIP) { // 只要该IP没有超出IP网段范围,就继续循环随机 121 | if TestAll { // 如果是测试全部IP 122 | for i := 0; i <= int(hosts); i++ { // 遍历IP最后一段最小值到最大值 123 | r.appendIPv4(byte(i) + minIP) 124 | } 125 | } else { // 随机IP的最后一段0.0.0.X 126 | r.appendIPv4(minIP + randIPEndWith(hosts)) 127 | } 128 | r.firstIP[14]++ // 0.0.(X+1).X 129 | if r.firstIP[14] == 0 { 130 | r.firstIP[13]++ // 0.(X+1).X.X 131 | if r.firstIP[13] == 0 { 132 | r.firstIP[12]++ // (X+1).X.X.X 133 | } 134 | } 135 | } 136 | } 137 | } 138 | 139 | // chooseIPv6 生成CIDR范围内的随机IPv6地址 140 | func (r *IPRanges) chooseIPv6() { 141 | if r.mask == "/128" { // 单个IP则无需随机,直接加入自身即可 142 | r.appendIP(r.firstIP) 143 | } else { 144 | for r.ipNet.Contains(r.firstIP) { // 只要该IP没有超出IP网段范围,就继续循环随机 145 | r.firstIP[15] = randIPEndWith(255) // 随机IP的最后一段 146 | r.firstIP[14] = randIPEndWith(255) // 随机IP的倒数第二段 147 | 148 | targetIP := make([]byte, len(r.firstIP)) 149 | copy(targetIP, r.firstIP) 150 | r.appendIP(targetIP) // 加入IP地址池 151 | 152 | for i := 13; i >= 0; i-- { // 从倒数第三位开始往前随机 153 | tempIP := r.firstIP[i] 154 | r.firstIP[i] += randIPEndWith(255) 155 | if r.firstIP[i] >= tempIP { 156 | break 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | // loadIPRanges 从文件或字符串中加载IP范围 164 | func loadIPRanges(IPFile string) []*net.IPAddr { 165 | ranges := newIPRanges() 166 | if IPText != "" { // 从参数中获取IP段数据 167 | IPs := strings.Split(IPText, ",") 168 | for _, IP := range IPs { 169 | IP = strings.TrimSpace(IP) 170 | if IP == "" { 171 | continue 172 | } 173 | ranges.parseCIDR(IP) 174 | if isIPv4(IP) { 175 | ranges.chooseIPv4() 176 | } else { 177 | ranges.chooseIPv6() 178 | } 179 | } 180 | } else { // 从文件中获取IP段数据 181 | if IPFile == "" { 182 | IPFile = defaultInputFile 183 | } 184 | file, err := os.Open(IPFile) 185 | if err != nil { 186 | log.Fatal(err) 187 | } 188 | defer file.Close() 189 | scanner := bufio.NewScanner(file) 190 | for scanner.Scan() { 191 | line := strings.TrimSpace(scanner.Text()) 192 | if line == "" { 193 | continue 194 | } 195 | ranges.parseCIDR(line) 196 | if isIPv4(line) { 197 | ranges.chooseIPv4() 198 | } else { 199 | ranges.chooseIPv6() 200 | } 201 | } 202 | } 203 | return ranges.ips 204 | } 205 | 206 | func RandomParseCIDR(IPFile string, parsedIPFile string) { 207 | ips := loadIPRanges(IPFile) // 获取IP列表 208 | if parsedIPFile == "" { 209 | parsedIPFile = defaultOutputFile 210 | } 211 | 212 | // 创建文件 213 | file, err := os.Create(parsedIPFile) 214 | if err != nil { 215 | log.Fatal(err) 216 | } 217 | defer file.Close() 218 | 219 | // 将IP地址写入文件 220 | for _, ip := range ips { 221 | ipStr := ip.String() // 将 *net.IPAddr 转换为字符串 222 | _, err := file.WriteString(ipStr + "\n") 223 | if err != nil { 224 | log.Fatal(err) 225 | } 226 | } 227 | fmt.Printf("\033[90mCIDR地址 %s 已解析并随机主机名,保存为 %s。\n如未取得优选结果,可修改参数并再次解析。\033[0m\n", IPFile, parsedIPFile) 228 | } 229 | 230 | // func main() { 231 | // // 定义命令行参数 232 | // flag.Parse() 233 | // args := flag.Args() 234 | 235 | // // 检查是否提供了文件路径 236 | // if len(args) < 1 { 237 | // fmt.Println("请提供文件路径(相对路径或绝对路径)和解析结果文件名(如留空会自动生成)") 238 | // return 239 | // } 240 | 241 | // // 文件路径 242 | // IPFile := args[0] 243 | // parsedIPFile := args[1] 244 | // if IPFile == "" { 245 | // IPFile = defaultInputFile 246 | // } 247 | // if parsedIPFile != "" { 248 | // pasedName := strings.Split(parsedIPFile, ".")[0] 249 | // parts := strings.Split(pasedName, "_") 250 | // if len(parts) > 1 && parts[1] != "" { 251 | // parsedIPFile = parts[1] + "_Pased.txt" 252 | // } else { 253 | // parsedIPFile = pasedName + "_Pased.txt" 254 | // } 255 | 256 | // } 257 | 258 | // randomParseCIDR(IPFile, parsedIPFile) 259 | // } 260 | -------------------------------------------------------------------------------- /task/unZip2txtFile.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "archive/zip" 5 | "io" 6 | "os" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type FileInfo struct { 13 | Name string 14 | TLS bool 15 | Port int 16 | Content []byte 17 | } 18 | 19 | func UnZip2txtFile(zipPath string, outputPath string) ([]FileInfo, error) { 20 | r, err := zip.OpenReader(zipPath) 21 | if err != nil { 22 | return nil, err 23 | } 24 | defer r.Close() 25 | 26 | var fileInfos []FileInfo 27 | pattern := regexp.MustCompile(`^(\w+)-(\d)-(\d+)\.txt$`) 28 | for _, f := range r.File { 29 | matches := pattern.FindStringSubmatch(f.Name) 30 | if len(matches) == 4 { 31 | tls, _ := strconv.Atoi(matches[2]) 32 | port, _ := strconv.Atoi(matches[3]) 33 | 34 | rc, err := f.Open() 35 | if err != nil { 36 | return nil, err 37 | } 38 | content, err := io.ReadAll(rc) 39 | rc.Close() 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | fileInfos = append(fileInfos, FileInfo{ 45 | Name: f.Name, 46 | TLS: tls == 1, 47 | Port: port, 48 | Content: content, 49 | }) 50 | } 51 | } 52 | 53 | if len(fileInfos) == 0 { 54 | // 如果不是 ASN 格式,则按照原来的逻辑处理 55 | return nil, mergeTextFiles(r, outputPath) 56 | } 57 | 58 | return fileInfos, nil 59 | } 60 | 61 | // 保留 mergeTextFiles 函数的实现... 62 | 63 | func mergeTextFiles(r *zip.ReadCloser, outputPath string) error { 64 | outputFile, err := os.Create(outputPath) 65 | if err != nil { 66 | return err 67 | } 68 | defer outputFile.Close() 69 | 70 | for _, f := range r.File { 71 | if !strings.HasSuffix(f.Name, ".txt") { 72 | continue 73 | } 74 | 75 | rc, err := f.Open() 76 | if err != nil { 77 | return err 78 | } 79 | 80 | _, err = io.Copy(outputFile, rc) 81 | if err != nil { 82 | rc.Close() 83 | return err 84 | } 85 | rc.Close() 86 | 87 | _, err = outputFile.WriteString("\n") 88 | if err != nil { 89 | return err 90 | } 91 | } 92 | 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /task/update.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "bufio" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | var baseLens = len("-IP 64.110.104.30 端口 443 位置:JP KIX 延迟 158 ms [ 489 ms ]") 16 | 17 | // func main() { 18 | // dataUpdate("result_test.csv", "example.com", "your_token") 19 | // } 20 | 21 | // 移除BOM(字节顺序标记) 22 | func removeBOM(data []byte) []byte { 23 | if len(data) >= 3 && data[0] == 0xef && data[1] == 0xbb && data[2] == 0xbf { 24 | return data[3:] 25 | } 26 | return data 27 | } 28 | 29 | // 发送HTTP GET请求并处理重试逻辑 30 | func sendGetRequest(client *http.Client, urlStr string, maxRetries int) (*http.Response, error) { 31 | var resp *http.Response 32 | var err error 33 | for i := 0; i < maxRetries; i++ { 34 | resp, err = client.Get(urlStr) 35 | if err == nil { 36 | return resp, nil 37 | } 38 | // fmt.Printf("请求失败(重试 %d/%d 次):%v\n", i+1, maxRetries, err) 39 | fmt.Printf("请求失败(重试 %d/%d 次)\n", i+1, maxRetries) 40 | time.Sleep(1 * time.Second) // 等待2秒后重试 41 | } 42 | return nil, err 43 | } 44 | 45 | // 更新数据 46 | func DataUpdate(fileName string, domain string, token string,baseLens int,countQualified int) { 47 | // 清除输出内容 48 | fmt.Print("\033[2J\033[0;0H") 49 | fmt.Printf("优选IP文件 %s 正在上传到 %s\n", fileName, domain) 50 | // 读取文件的前65行内容 51 | file, err := os.Open(fileName) 52 | if err != nil { 53 | fmt.Printf("读取文件时出错: %v\n", err) 54 | return 55 | } 56 | defer file.Close() 57 | 58 | scanner := bufio.NewScanner(file) 59 | var lines []string 60 | for i := 0; i < 65 && scanner.Scan(); i++ { 61 | lines = append(lines, scanner.Text()) 62 | } 63 | if err := scanner.Err(); err != nil { 64 | fmt.Printf("扫描文件时出错: %v\n", err) 65 | return 66 | } 67 | 68 | // 将内容转换为UTF-8并进行Base64编码 69 | content := strings.Join(lines, "\n") 70 | contentBytes := removeBOM([]byte(content)) 71 | base64Text := base64.StdEncoding.EncodeToString(contentBytes) 72 | 73 | // 构造更新URL 74 | updateUrlStr := fmt.Sprintf("https://%s/%s?token=%s&b64=%s&v=%d", domain, url.PathEscape(fileName), token, url.QueryEscape(base64Text), time.Now().Unix()) 75 | 76 | // 设置HTTP客户端,启用Keep-Alive和重试逻辑 77 | client := &http.Client{ 78 | Transport: &http.Transport{ 79 | MaxIdleConns: 10, // 最大空闲连接数 80 | IdleConnTimeout: 30 * time.Second, // 空闲连接的超时时间 81 | MaxIdleConnsPerHost: 2, // 每个主机的最大空闲连接数 82 | DisableKeepAlives: false, // 启用 Keep-Alive 83 | // TLSHandshakeTimeout: 10 * time.Second, 84 | }, 85 | Timeout: time.Second * 30, // 设置单次请求超时时间为30秒 86 | } 87 | 88 | // 发送更新请求 89 | resp, err := sendGetRequest(client, updateUrlStr, 5) // 尝试重试5次 90 | if err != nil { 91 | fmt.Printf("自动更新失败,请手动上传文件或重新执行程序!\n %v\n", err) 92 | return 93 | } 94 | defer resp.Body.Close() 95 | 96 | // 检查更新请求的状态码 97 | if resp.StatusCode != http.StatusOK { 98 | fmt.Printf("更新请求失败,状态码: %d\n", resp.StatusCode) 99 | return 100 | } 101 | repeatCount := baseLens - len("发起数据更新请求[ok]") 102 | fmt.Printf("发起数据更新请求%s[\033[32mok\033[0m]\n", strings.Repeat(".", repeatCount)) 103 | 104 | // 构造读取URL 105 | readUrlStr := fmt.Sprintf("https://%s/%s?token=%s&v=%d", domain, url.PathEscape(fileName), token, time.Now().Unix()) 106 | 107 | // 发送读取请求 108 | resp, err = sendGetRequest(client, readUrlStr, 5) // 尝试重试5次 109 | if err != nil { 110 | fmt.Printf("发送读取请求时出错: %v\n", err) 111 | return 112 | } 113 | defer resp.Body.Close() 114 | 115 | // 检查读取请求的状态码 116 | if resp.StatusCode != http.StatusOK { 117 | fmt.Printf("读取请求失败,状态码: %d\n", resp.StatusCode) 118 | return 119 | } 120 | 121 | // 读取响应内容 122 | readBody, err := io.ReadAll(resp.Body) 123 | if err != nil { 124 | fmt.Printf("读取响应时出错: %v\n", err) 125 | return 126 | } 127 | 128 | // 将读取到的内容移除BOM 129 | responseContent := removeBOM(readBody) 130 | originalContent := contentBytes 131 | 132 | // 比较发送的内容和响应内容 133 | equ := (string(responseContent) == string(originalContent)) 134 | 135 | // 根据比较结果判断是否成功 136 | UrlStr := fmt.Sprintf("https://%s/%s?token=%s", domain, url.PathEscape(fileName), token) 137 | repeatCount = baseLens - len("验证数据更新结果[ok]") 138 | if equ { 139 | fmt.Printf("验证数据更新结果%s[\033[32mok\033[0m]\n\n", strings.Repeat(".", repeatCount)) 140 | // fmt.Println(string(responseContent)) 141 | fmt.Printf("\n\033[90m优选IP\033[0m \033[90;4m%s\033[0m \033[90m已成功更新 %d 条数据至:\033[0m\n\033[34m%s\033[0m\n", fileName, countQualified, UrlStr) 142 | } else { 143 | fmt.Printf("验证数据更新结果%s[\033[31mX\033[0m]\n", strings.Repeat(".", repeatCount)) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /task/windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package task 5 | 6 | import ( 7 | "fmt" 8 | "golang.org/x/sys/windows/registry" 9 | ) 10 | 11 | func CheckProxyEnabled() bool { 12 | k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE) 13 | if err != nil { 14 | fmt.Println("无法打开注册表键:", err) 15 | return false 16 | } 17 | defer k.Close() 18 | 19 | proxyEnable, _, err := k.GetIntegerValue("ProxyEnable") 20 | if err != nil { 21 | fmt.Println("无法读取ProxyEnable值:", err) 22 | return false 23 | } 24 | 25 | return proxyEnable == 1 26 | } 27 | -------------------------------------------------------------------------------- /tool/QueryDownload.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/csv" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "path" 11 | "path/filepath" 12 | //"strings" 13 | "sync" 14 | 15 | "github.com/PuerkitoBio/goquery" 16 | ) 17 | 18 | // fetchLinks 从指定的URL获取所有以.csv结尾的下载链接 19 | func fetchLinks(baseURL string) ([]string, error) { 20 | res, err := http.Get(baseURL) 21 | if err != nil { 22 | return nil, err 23 | } 24 | defer res.Body.Close() 25 | 26 | if res.StatusCode != 200 { 27 | return nil, fmt.Errorf("status code error: %d %s", res.StatusCode, res.Status) 28 | } 29 | 30 | doc, err := goquery.NewDocumentFromReader(res.Body) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | var links []string 36 | doc.Find(".fname1").Each(func(index int, item *goquery.Selection) { 37 | link, _ := item.Attr("href") 38 | //if strings.HasSuffix(link, ".csv") { 39 | // 处理相对路径 40 | absoluteURL := resolveURL(baseURL, link) 41 | links = append(links, absoluteURL) 42 | //} 43 | }) 44 | 45 | return links, nil 46 | } 47 | 48 | // resolveURL 将相对路径转换为绝对路径 49 | func resolveURL(baseURL, relativeURL string) string { 50 | base, err := url.Parse(baseURL) 51 | if err != nil { 52 | return "" 53 | } 54 | ref, err := url.Parse(relativeURL) 55 | if err != nil { 56 | return "" 57 | } 58 | return base.ResolveReference(ref).String() 59 | } 60 | 61 | // downloadFileWg 下载指定URL的文件并保存到本地 62 | func downloadFileWg(baseURL, fileURL, baseDir string, wg *sync.WaitGroup) { 63 | defer wg.Done() 64 | 65 | // 获取文件名和目录 66 | u, err := url.Parse(fileURL) 67 | if err != nil { 68 | fmt.Println("Error parsing URL:", err) 69 | return 70 | } 71 | filePath := u.Path 72 | fileName := path.Base(filePath) 73 | dir := path.Join(baseDir, path.Dir(filePath)) 74 | 75 | // 创建目录 76 | if err := os.MkdirAll(dir, os.ModePerm); err != nil { 77 | fmt.Println("Error creating directory:", err) 78 | return 79 | } 80 | 81 | // 创建文件 82 | out, err := os.Create(filepath.Join(dir, fileName)) 83 | if err != nil { 84 | fmt.Println("Error creating file:", err) 85 | return 86 | } 87 | defer out.Close() 88 | 89 | // 发送HTTP请求下载文件 90 | resp, err := http.Get(fileURL) 91 | if err != nil { 92 | fmt.Println("Error downloading file:", err) 93 | return 94 | } 95 | defer resp.Body.Close() 96 | 97 | // 将响应内容写入文件 98 | _, err = io.Copy(out, resp.Body) 99 | if err != nil { 100 | fmt.Println("Error writing to file:", err) 101 | return 102 | } 103 | 104 | fmt.Println("Downloaded:", filepath.Join(dir, fileName)) 105 | } 106 | 107 | func main() { 108 | baseURL := "https://cloud.bhqt.fun/?dir=/碧海反代IP测试工具" 109 | 110 | // 获取程序当前工作目录 111 | baseDir, err := os.Getwd() 112 | if err != nil { 113 | fmt.Println("Error getting current directory:", err) 114 | return 115 | } 116 | baseDir= baseDir+"/Test" 117 | // 获取所有下载链接 118 | links, err := fetchLinks(baseURL) 119 | if err != nil { 120 | fmt.Println("Error fetching links:", err) 121 | return 122 | } 123 | 124 | // 创建CSV文件保存链接 125 | file, err := os.Create("download_links.csv") 126 | if err != nil { 127 | fmt.Println("Cannot create file:", err) 128 | return 129 | } 130 | defer file.Close() 131 | 132 | writer := csv.NewWriter(file) 133 | defer writer.Flush() 134 | writer.Write([]string{"Link"}) 135 | 136 | var wg sync.WaitGroup 137 | for _, link := range links { 138 | writer.Write([]string{link}) // 将链接写入CSV文件 139 | wg.Add(1) 140 | go downloadFileWg(baseURL, link, baseDir, &wg) // 并发下载文件 141 | } 142 | 143 | wg.Wait() // 等待所有下载完成 144 | fmt.Println("All files downloaded and links saved to download_links.csv") 145 | } 146 | -------------------------------------------------------------------------------- /tool/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sinspired/CloudflareBestIP/tool 2 | 3 | go 1.22.4 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.9.2 7 | github.com/mattn/go-ieproxy v0.0.12 8 | ) 9 | 10 | require ( 11 | github.com/andybalholm/cascadia v1.3.2 // indirect 12 | golang.org/x/net v0.25.0 // indirect 13 | golang.org/x/sys v0.20.0 // indirect 14 | golang.org/x/text v0.15.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /tool/go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= 2 | github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= 3 | github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= 4 | github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= 5 | github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M= 6 | github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c= 7 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 8 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 9 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 10 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 11 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 12 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 13 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 14 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 15 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 16 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 17 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 18 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 19 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 20 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 21 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 22 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 23 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 24 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 25 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 27 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 30 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 31 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 32 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 33 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 34 | golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 35 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 36 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 37 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 38 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 39 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 40 | golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= 41 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 42 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 43 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 44 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 45 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 46 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 47 | -------------------------------------------------------------------------------- /tool/gptProxyTest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | "os" 13 | "strings" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | type IPPort struct { 19 | IP string 20 | Port string 21 | } 22 | 23 | const ( 24 | timeout = 50 * time.Second // 连接超时时间 25 | maxDuration = 50 * time.Second // 请求和读取响应的最大持续时间 26 | traceURL = "https://cloudflare.com/cdn-cgi/trace" // 请求trace URL 27 | gptURL = "https://chatgpt.com" // 请求GPT URL 28 | ) 29 | 30 | // checkTrace 使用给定的IP和端口访问cloudflare.com/cdn-cgi/trace 31 | func checkTrace(ipPort IPPort, wg *sync.WaitGroup, validIPs chan<- IPPort) { 32 | defer wg.Done() 33 | resp := httping(ipPort.IP, ipPort.Port, traceURL) 34 | if resp.StatusCode == http.StatusOK { 35 | validIPs <- ipPort 36 | } 37 | } 38 | 39 | // checkGptProxy 使用给定的IP和端口访问chatgpt.com,检查返回的页面是否包含"block" 40 | func checkGptProxy(ipPort IPPort, wg *sync.WaitGroup, results chan<- string) { 41 | defer wg.Done() 42 | resp := httping(ipPort.IP, ipPort.Port, gptURL) 43 | var buf bytes.Buffer 44 | _, err := io.Copy(&buf, resp.Body) 45 | if err != nil { 46 | fmt.Printf("读取响应体错误: %v\n", err) 47 | return 48 | } 49 | content := buf.String() 50 | if strings.Contains(content, "block") { 51 | results <- fmt.Sprintf("IP %s:%s - 被封锁", ipPort.IP, ipPort.Port) 52 | } else { 53 | results <- fmt.Sprintf("IP %s:%s - 正常", ipPort.IP, ipPort.Port) 54 | } 55 | } 56 | 57 | func httping(IP, Port, requestURL string) *http.Response { 58 | dialer := &net.Dialer{ 59 | Timeout: timeout, 60 | KeepAlive: 0, 61 | } 62 | conn, err := dialer.DialContext(context.Background(), "tcp", net.JoinHostPort(IP, Port)) 63 | if err != nil { 64 | fmt.Printf("建立TCP连接错误 %s:%s - %s\n", IP, Port, err) 65 | return &http.Response{} 66 | } 67 | defer conn.Close() 68 | client := http.Client{ 69 | Transport: &http.Transport{ 70 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 71 | return conn, nil 72 | }, 73 | TLSClientConfig: &tls.Config{ 74 | InsecureSkipVerify: true, 75 | }, 76 | }, 77 | Timeout: maxDuration, 78 | } 79 | req, _ := http.NewRequest("GET", requestURL, nil) 80 | req.Header.Set("User-Agent", "Mozilla/5.0") 81 | req.Close = true 82 | resp, err := client.Do(req) 83 | if err != nil { 84 | fmt.Printf("访问错误 %s - %s\n", net.JoinHostPort(IP, Port), err) 85 | return &http.Response{} 86 | } 87 | defer resp.Body.Close() 88 | return resp 89 | } 90 | 91 | func main() { 92 | flag.Parse() 93 | args := flag.Args() 94 | defaultIPPorts := []IPPort{ 95 | {"8.8.8.8", "53"}, 96 | } 97 | var ipPorts []IPPort 98 | if len(args) == 0 { 99 | fmt.Println("未提供IP:Port参数,使用默认值") 100 | ipPorts = defaultIPPorts 101 | } else { 102 | for _, arg := range strings.Split(args[0], ",") { 103 | parts := strings.Split(arg, ":") 104 | if len(parts) == 9 { 105 | ipv6 := strings.Join(parts[0:8], ":") 106 | port := parts[8] 107 | ipPorts = append(ipPorts, IPPort{ipv6, port}) 108 | } else if len(parts) == 2 { 109 | ipPorts = append(ipPorts, IPPort{IP: parts[0], Port: parts[1]}) 110 | } else { 111 | fmt.Printf("无效的IP:Port格式: %s\n", arg) 112 | continue 113 | } 114 | } 115 | } 116 | var wg sync.WaitGroup 117 | validIPs := make(chan IPPort, len(ipPorts)) 118 | results := make(chan string, len(ipPorts)) 119 | for _, ipPort := range ipPorts { 120 | wg.Add(1) 121 | go checkTrace(ipPort, &wg, validIPs) 122 | } 123 | wg.Wait() 124 | close(validIPs) 125 | for ipPort := range validIPs { 126 | wg.Add(1) 127 | go checkGptProxy(ipPort, &wg, results) 128 | } 129 | wg.Wait() 130 | close(results) 131 | file, err := os.Create("results.txt") 132 | if err != nil { 133 | fmt.Printf("创建文件错误: %v\n", err) 134 | return 135 | } 136 | defer file.Close() 137 | for result := range results { 138 | fmt.Println(result) 139 | file.WriteString(result + "\n") 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tool/gptTest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | "regexp" 13 | "strings" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | type IPPort struct { 19 | IP string 20 | Port string 21 | } 22 | 23 | const ( 24 | timeout = 50 * time.Second // 连接超时时间 25 | maxDuration = 50 * time.Second // 请求和读取响应的最大持续时间 26 | // requestURL = "https://speed.cloudflare.com/cdn-cgi/trace" // 请求trace URL 27 | // requestURL = "https://chatgpt.com/cdn-cgi/trace/" // 请求trace URL 28 | requestURL = "https://chatgpt.com" // 请求trace URL 29 | ) 30 | 31 | // checkIPPort 尝试连接到给定的IP和端口,并发送HTTP请求以获取响应内容 32 | func checkIPPort(ipPort IPPort, wg *sync.WaitGroup) { 33 | defer wg.Done() // 确保WaitGroup计数器在函数结束时递减 34 | 35 | // 设置连接超时的拨号器 36 | dialer := &net.Dialer{ 37 | Timeout: timeout, 38 | KeepAlive: 0, 39 | } 40 | 41 | // 创建一个带有超时的连接 42 | conn, err := dialer.DialContext(context.Background(), "tcp", net.JoinHostPort(ipPort.IP, ipPort.Port)) 43 | if err != nil { 44 | fmt.Printf("建立TCP连接错误 %s:%s - %s\n", ipPort.IP, ipPort.Port, err) 45 | return 46 | } 47 | defer conn.Close() 48 | 49 | // 设置HTTP客户端,使用自定义的拨号器 50 | client := http.Client{ 51 | Transport: &http.Transport{ 52 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 53 | return conn, nil 54 | }, 55 | // 为HTTPS设置TLS配置 56 | TLSClientConfig: &tls.Config{ 57 | InsecureSkipVerify: true, // 如果需要验证证书,请删除此行 58 | }, 59 | }, 60 | Timeout: maxDuration, 61 | } 62 | 63 | // 创建新的HTTP GET请求 64 | req, _ := http.NewRequest("GET", requestURL, nil) 65 | 66 | // 设置请求头中的用户代理 67 | req.Header.Set("User-Agent", "Mozilla/5.0") 68 | req.Close = true 69 | 70 | // 发送请求并获取响应 71 | resp, err := client.Do(req) 72 | if err != nil { 73 | fmt.Printf("访问错误 %s - %s\n", net.JoinHostPort(ipPort.IP, ipPort.Port), err) 74 | return 75 | } 76 | defer resp.Body.Close() 77 | 78 | // 将响应体内容复制到缓冲区 79 | var buf bytes.Buffer 80 | _, err = io.Copy(&buf, resp.Body) 81 | if err != nil { 82 | fmt.Printf("读取响应体错误: %v\n", err) 83 | return 84 | } 85 | 86 | content := buf.String() 87 | // fmt.Printf("来自 %s 的响应内容:\n%s\n", net.JoinHostPort(ipPort.IP, ipPort.Port), content) 88 | if strings.Contains(content, "html") { 89 | fmt.Printf("来自 %s 的请求,成功\n", net.JoinHostPort(ipPort.IP, ipPort.Port)) 90 | } 91 | fmt.Println(resp.Header) 92 | fmt.Println(resp.StatusCode) 93 | fmt.Println(resp.Header.Get("Cf-Mitigated")) 94 | if strings.Contains(content, "blocked") { 95 | fmt.Println("已被封锁") 96 | } else { 97 | fmt.Println("网页端正常") 98 | } 99 | // 检查响应体是否包含指定的用户代理 100 | if strings.Contains(content, "uag=Mozilla/5.0") { 101 | // 使用正则表达式提取colo字段和loc字段 102 | coloRegex := regexp.MustCompile(`colo=([A-Z]+)`) 103 | locRegex := regexp.MustCompile(`loc=([A-Z]+)`) 104 | 105 | coloMatches := coloRegex.FindStringSubmatch(content) 106 | locMatches := locRegex.FindStringSubmatch(content) 107 | 108 | if len(coloMatches) > 1 && len(locMatches) > 1 { 109 | colo := coloMatches[1] 110 | loc := locMatches[1] 111 | fmt.Printf("IP %s:%s - colo: %s, loc: %s\n", ipPort.IP, ipPort.Port, colo, loc) 112 | } else { 113 | fmt.Printf("未找到 colo 或 loc 字段 - IP %s:%s\n", ipPort.IP, ipPort.Port) 114 | } 115 | } 116 | } 117 | 118 | func main() { 119 | // 定义命令行参数 120 | flag.Parse() 121 | args := flag.Args() 122 | 123 | // 默认的IP和端口 124 | defaultIPPorts := []IPPort{ 125 | {"8.8.8.8", "53"}, 126 | } 127 | 128 | var ipPorts []IPPort 129 | 130 | if len(args) == 0 { 131 | fmt.Println("未提供IP:Port参数,使用默认值") 132 | ipPorts = defaultIPPorts 133 | } else { 134 | // 解析输入的IP和端口列表 135 | for _, arg := range strings.Split(args[0], ",") { 136 | parts := strings.Split(arg, ":") 137 | if len(parts) == 9 { 138 | // ipv6格式:2a05:d014:ed:9600:f52b:ab01:6bb:bc9d 139 | ipv6 := strings.Join(parts[0:8], ":") 140 | port := parts[8] 141 | ipPorts = append(ipPorts, IPPort{ipv6, port}) 142 | } else if len(parts) == 2 { 143 | ipPorts = append(ipPorts, IPPort{IP: parts[0], Port: parts[1]}) 144 | } else { 145 | fmt.Printf("无效的IP:Port格式: %s\n", arg) 146 | continue 147 | } 148 | } 149 | } 150 | 151 | var wg sync.WaitGroup 152 | 153 | // 逐个检查IP和端口,使用goroutine实现并发 154 | for _, ipPort := range ipPorts { 155 | wg.Add(1) 156 | go checkIPPort(ipPort, &wg) 157 | } 158 | 159 | // 等待所有goroutine完成 160 | wg.Wait() 161 | } 162 | -------------------------------------------------------------------------------- /tool/makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | .PHONY: all mergeCSV QueryDownload tcpTest 4 | 5 | all: mergeCSV QueryDownload tcpTest 6 | 7 | mergeCSV: 8 | go run mergeCSV.go $(f) 9 | 10 | QueryDownload: 11 | go run QueryDownload.go 12 | 13 | tcpTest: 14 | go run tcpTest.go $(ip) 15 | 16 | buildreparse: 17 | go build -o ../reParseResultTxtFile.exe reParseResultTxtFile.go 18 | copy "..\reParseResultTxtFile.exe" "F:\worknow\iplab\" 19 | copy "..\reParseResultTxtFile.exe" "F:\" 20 | 21 | buildfofa: 22 | go build -o ../processFofaCSV.exe processFofaCSV.go 23 | copy "..\processFofaCSV.exe" "F:\" -------------------------------------------------------------------------------- /tool/mergeCSV.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/csv" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | func main() { 15 | // 定义命令行参数 16 | flag.Parse() 17 | args := flag.Args() 18 | 19 | // 检查是否提供了文件夹路径 20 | if len(args) < 1 { 21 | fmt.Println("请提供文件夹路径(相对路径或绝对路径)和结果文件名,例如:go run mergeCSV.go folderpath mergedTxtFile") 22 | return 23 | } 24 | 25 | // 文件夹路径 26 | folderPath := args[0] 27 | mergedTxtFile := args[1] 28 | if mergedTxtFile == "" { 29 | if strings.Contains(folderPath, "ipscanner") { 30 | mergedTxtFile = "ip_Scanner.txt" 31 | } else { 32 | mergedTxtFile = "ip_Merged.txt" 33 | } 34 | } 35 | 36 | // 创建一个新的 TXT 文件来存储结果 37 | // https://codeload.github.com/ip-scanner/cloudflare/zip/refs/heads/main 38 | outputFile, err := os.Create(mergedTxtFile) 39 | if err != nil { 40 | fmt.Println("无法创建输出文件:", err) 41 | return 42 | } 43 | defer outputFile.Close() 44 | 45 | writer := bufio.NewWriter(outputFile) 46 | defer writer.Flush() 47 | 48 | // 遍历文件夹内所有 CSV 文件 49 | err = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error { 50 | if err != nil { 51 | return err 52 | } 53 | if !info.IsDir() && filepath.Ext(path) == ".csv" { 54 | file, err := os.Open(path) 55 | if err != nil { 56 | fmt.Println("无法打开文件:", err) 57 | return err 58 | } 59 | defer file.Close() 60 | 61 | reader := csv.NewReader(file) 62 | for { 63 | record, err := reader.Read() 64 | if err == io.EOF { 65 | break 66 | } 67 | if err != nil { 68 | fmt.Println("读取文件错误:", err) 69 | return err 70 | } 71 | // 只写入第一列 72 | if len(record) > 0 { 73 | writer.WriteString(record[0] + "\n") 74 | } 75 | } 76 | } 77 | return nil 78 | }) 79 | if err != nil { 80 | fmt.Println("遍历文件夹错误:", err) 81 | return 82 | } 83 | 84 | fmt.Printf("合并完成,结果保存在 %s\n", mergedTxtFile) 85 | } 86 | -------------------------------------------------------------------------------- /tool/processFofaCSV.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/zip" 5 | "bufio" 6 | "encoding/csv" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | ) 15 | 16 | var ( 17 | ipFile_NoTLS = "ip_Fofa-0.txt" 18 | ipFile_TLS = "ip_Fofa.txt" 19 | zipFile = "Fofas.zip" 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | args := flag.Args() 25 | 26 | var folder string 27 | defaultFolderPath := "FofaCSV" 28 | 29 | // 检查是否提供了文件夹路径 30 | if len(args) < 1 { 31 | if _, err := os.Stat(defaultFolderPath); os.IsNotExist(err) { 32 | // 如果默认文件夹不存在 33 | fmt.Println("请提供文件夹路径(相对路径或绝对路径)和导出文件夹") 34 | return 35 | } 36 | folder = defaultFolderPath 37 | } else { 38 | inputFolderPath := args[0] 39 | folder = inputFolderPath 40 | } 41 | 42 | outputFolder := folder + "Output" 43 | os.Mkdir(outputFolder, 0o755) 44 | 45 | fmt.Println("开始处理文件夹:", folder) 46 | 47 | // 遍历文件夹中的所有CSV文件 48 | files, err := ioutil.ReadDir(folder) 49 | if err != nil { 50 | fmt.Println("读取文件夹错误:", err) 51 | return 52 | } 53 | 54 | var zipFiles []string 55 | 56 | for _, file := range files { 57 | if filepath.Ext(file.Name()) == ".csv" { 58 | fmt.Println("处理文件:", file.Name()) 59 | processCSV(filepath.Join(folder, file.Name()), outputFolder, &zipFiles) 60 | } 61 | } 62 | // fmt.Printf("\nNoTLS IP文件已生成:%s\n", filepath.Join(outputFolder, ipFile_NoTLS)) 63 | fmt.Printf("\ntlsIP文件已生成:%s\n", filepath.Join(outputFolder, ipFile_TLS)) 64 | 65 | // 压缩生成的txt文件 66 | zipFilePath := filepath.Join(outputFolder, zipFile) 67 | err = createZip(zipFilePath, zipFiles) 68 | if err != nil { 69 | fmt.Println("压缩文件错误:", err) 70 | return 71 | } 72 | 73 | fmt.Println("压缩文件已生成:", zipFilePath) 74 | 75 | // 复制文件到程序所在文件夹 76 | copyFile(zipFilePath, zipFile) 77 | // copyFile(filepath.Join(outputFolder, ipFile_NoTLS), ipFile_NoTLS) 78 | copyFile(filepath.Join(outputFolder, ipFile_TLS), ipFile_TLS) 79 | 80 | fmt.Println("文件复制完成") 81 | } 82 | 83 | func processCSV(filePath, outputFolder string, zipFiles *[]string) { 84 | file, err := os.Open(filePath) 85 | if err != nil { 86 | fmt.Println("打开文件错误:", err) 87 | return 88 | } 89 | defer file.Close() 90 | 91 | reader := csv.NewReader(file) 92 | headers, err := reader.Read() 93 | if err != nil { 94 | fmt.Println("读取CSV标题错误:", err) 95 | return 96 | } 97 | 98 | // 获取各列的索引 99 | var ipIndex, portIndex, countryIndex, protocolIndex, asnIndex, orgIndex int 100 | for i, header := range headers { 101 | switch header { 102 | case "ip": 103 | ipIndex = i 104 | case "port": 105 | portIndex = i 106 | case "country": 107 | countryIndex = i 108 | case "protocol": 109 | protocolIndex = i 110 | case "as_organization": 111 | orgIndex = i 112 | case "as_number": 113 | asnIndex = i 114 | } 115 | } 116 | 117 | var ips, ports, countries, protocols,asns, orgs []string 118 | for { 119 | record, err := reader.Read() 120 | if err == io.EOF { 121 | break 122 | } 123 | if err != nil { 124 | fmt.Println("读取CSV记录错误:", err) 125 | return 126 | } 127 | ips = append(ips, record[ipIndex]) 128 | ports = append(ports, record[portIndex]) 129 | countries = append(countries, record[countryIndex]) 130 | protocols = append(protocols, record[protocolIndex]) 131 | asns =append(asns, record[asnIndex]) 132 | orgs = append(orgs, record[orgIndex]) 133 | } 134 | cleanIPs := []string{} 135 | for i, ip := range ips { 136 | if !strings.Contains(asns[i], "45102") && !strings.Contains(orgs[i], "Cloudflare") { 137 | cleanIPs = append(cleanIPs, ip) 138 | } 139 | } 140 | uniqueIPs := unique(cleanIPs) 141 | 142 | if allEqual(ports) { 143 | protocol := "0" 144 | if protocols[0] == "https" { 145 | protocol = "1" 146 | } 147 | if allEqual(countries) { 148 | fileName := fmt.Sprintf("%s-%s-%s.txt", countries[0], protocol, ports[0]) 149 | writeToFile(filepath.Join(outputFolder, fileName), uniqueIPs) 150 | *zipFiles = append(*zipFiles, filepath.Join(outputFolder, fileName)) 151 | } else { 152 | fileName := fmt.Sprintf("Fofa-%s-%s.txt", protocol, ports[0]) 153 | writeToFile(filepath.Join(outputFolder, fileName), uniqueIPs) 154 | *zipFiles = append(*zipFiles, filepath.Join(outputFolder, fileName)) 155 | } 156 | } else { 157 | httpIPs, httpsIPs := []string{}, []string{} 158 | for i, ip := range ips { 159 | if !strings.Contains(asns[i], "45102") && !strings.Contains(orgs[i], "Cloudflare") && ip != "" { 160 | if protocols[i] == "http" { 161 | httpIPs = append(httpIPs, fmt.Sprintf("%s:%s", ip, ports[i])) 162 | } else { 163 | httpsIPs = append(httpsIPs, fmt.Sprintf("%s:%s", ip, ports[i])) 164 | } 165 | } 166 | } 167 | writeToFile(filepath.Join(outputFolder, ipFile_NoTLS), unique(httpIPs)) 168 | writeToFile(filepath.Join(outputFolder, ipFile_TLS), unique(httpsIPs)) 169 | } 170 | } 171 | 172 | func allEqual(slice []string) bool { 173 | for _, v := range slice { 174 | if v != slice[0] { 175 | return false 176 | } 177 | } 178 | return true 179 | } 180 | 181 | func unique(slice []string) []string { 182 | keys := make(map[string]bool) 183 | list := []string{} 184 | for _, entry := range slice { 185 | if _, value := keys[entry]; !value { 186 | keys[entry] = true 187 | list = append(list, entry) 188 | } 189 | } 190 | return list 191 | } 192 | 193 | func writeToFile(filePath string, data []string) { 194 | // 创建一个 map 用于存储唯一的行 195 | lines := make(map[string]struct{}) 196 | 197 | // 如果文件存在,读取现有文件内容 198 | file, err := os.OpenFile(filePath, os.O_RDONLY, 0o644) 199 | if err == nil { 200 | scanner := bufio.NewScanner(file) 201 | for scanner.Scan() { 202 | line := strings.TrimSpace(scanner.Text()) // 去除行首尾的空白字符 203 | if line != "" { // 过滤空行 204 | lines[line] = struct{}{} 205 | } 206 | } 207 | file.Close() // 关闭文件 208 | } 209 | 210 | // 将新数据添加到 map 中 211 | for _, line := range data { 212 | line = strings.TrimSpace(line) // 去除行首尾的空白字符 213 | if line != "" { // 过滤空行 214 | lines[line] = struct{}{} 215 | } 216 | } 217 | 218 | // 以写模式打开文件(清空文件内容) 219 | file, err = os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) 220 | if err != nil { 221 | fmt.Println("写入文件错误:", err) 222 | return 223 | } 224 | defer file.Close() // 函数结束前关闭文件 225 | 226 | // 将唯一的行写入文件 227 | for line := range lines { 228 | _, err := file.WriteString(line + "\n") 229 | if err != nil { 230 | fmt.Println("写入文件错误:", err) 231 | return 232 | } 233 | } 234 | } 235 | 236 | func createZip(zipFileName string, files []string) error { 237 | newZipFile, err := os.Create(zipFileName) 238 | if err != nil { 239 | return err 240 | } 241 | defer newZipFile.Close() 242 | 243 | zipWriter := zip.NewWriter(newZipFile) 244 | defer zipWriter.Close() 245 | 246 | for _, file := range files { 247 | if err := addFileToZip(zipWriter, file); err != nil { 248 | return err 249 | } 250 | } 251 | return nil 252 | } 253 | 254 | func addFileToZip(zipWriter *zip.Writer, filename string) error { 255 | fileToZip, err := os.Open(filename) 256 | if err != nil { 257 | return err 258 | } 259 | defer fileToZip.Close() 260 | 261 | info, err := fileToZip.Stat() 262 | if err != nil { 263 | return err 264 | } 265 | 266 | header, err := zip.FileInfoHeader(info) 267 | if err != nil { 268 | return err 269 | } 270 | 271 | header.Name = filepath.Base(filename) 272 | header.Method = zip.Deflate 273 | 274 | writer, err := zipWriter.CreateHeader(header) 275 | if err != nil { 276 | return err 277 | } 278 | 279 | _, err = io.Copy(writer, fileToZip) 280 | return err 281 | } 282 | 283 | func copyFile(src, dst string) { 284 | if _, err := os.Stat(src); os.IsNotExist(err) { 285 | // 如不存在,取消复制 286 | return 287 | } 288 | sourceFile, err := os.Open(src) 289 | if err != nil { 290 | fmt.Println("复制文件错误:", err) 291 | return 292 | } 293 | defer sourceFile.Close() 294 | 295 | destinationFile, err := os.Create(dst) 296 | if err != nil { 297 | fmt.Println("复制文件错误:", err) 298 | return 299 | } 300 | defer destinationFile.Close() 301 | 302 | _, err = io.Copy(destinationFile, sourceFile) 303 | if err != nil { 304 | fmt.Println("复制文件错误:", err) 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /tool/reParseResultTxtFile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | "os" 13 | "regexp" 14 | "strings" 15 | "sync" 16 | "time" 17 | ) 18 | 19 | // IPInfo 用于存储从API返回的IP信息 20 | type IPInfo struct { 21 | IP string `json:"ip"` 22 | Company CompanyInfo `json:"company"` 23 | ASN ASNInfo `json:"asn"` 24 | Location LocationInfo `json:"location"` 25 | } 26 | 27 | // CompanyInfo 用于存储公司信息 28 | type CompanyInfo struct { 29 | Name string `json:"name"` 30 | Type string `json:"type"` 31 | } 32 | 33 | // ASNInfo 用于存储ASN信息 34 | type ASNInfo struct { 35 | ASN int `json:"asn"` 36 | Org string `json:"org"` 37 | Country string `json:"country"` 38 | Type string `json:"type"` 39 | } 40 | 41 | // LocationInfo 用于存储位置信息 42 | type LocationInfo struct { 43 | Country_code string `json:"country_code"` 44 | } 45 | 46 | // 位置信息结构体 47 | 48 | type location struct { 49 | Iata string `json:"iata"` 50 | Lat float64 `json:"lat"` 51 | Lon float64 `json:"lon"` 52 | Cca2 string `json:"cca2"` 53 | Region string `json:"region"` 54 | City string `json:"city"` 55 | } 56 | 57 | const ( 58 | requestURL = "https://speed.cloudflare.com/cdn-cgi/trace" // 请求trace URL 59 | locationsJsonUrl = "https://speed.cloudflare.com/locations" // location.json下载 URL 60 | ) 61 | 62 | var locationMap map[string]location // IP位置数据 63 | // 读取机场信息 64 | func readLocationData() { 65 | var locations []location 66 | if _, err := os.Stat("locations.json"); os.IsNotExist(err) { 67 | fmt.Println("正在从 " + locationsJsonUrl + " 下载 locations.json") 68 | 69 | resp, err := http.Get(locationsJsonUrl) 70 | if err != nil { 71 | fmt.Printf("下载失败: %v\n", err) 72 | return 73 | } 74 | defer resp.Body.Close() 75 | 76 | body, err := io.ReadAll(resp.Body) 77 | if err != nil { 78 | fmt.Printf("无法读取响应体: %v\n", err) 79 | return 80 | } 81 | 82 | err = json.Unmarshal(body, &locations) 83 | if err != nil { 84 | fmt.Printf("无法解析JSON: %v\n", err) 85 | return 86 | } 87 | file, err := os.Create("locations.json") 88 | if err != nil { 89 | fmt.Printf("无法创建文件: %v\n", err) 90 | return 91 | } 92 | defer file.Close() 93 | 94 | _, err = file.Write(body) 95 | if err != nil { 96 | fmt.Printf("无法写入文件: %v\n", err) 97 | return 98 | } 99 | fmt.Println("\033[32m成功下载并创建 location.json\033[0m") 100 | } else { 101 | file, err := os.Open("locations.json") 102 | if err != nil { 103 | fmt.Printf("无法打开文件: %v\n", err) 104 | return 105 | } 106 | defer file.Close() 107 | 108 | body, err := io.ReadAll(file) 109 | if err != nil { 110 | fmt.Printf("无法读取文件: %v\n", err) 111 | return 112 | } 113 | 114 | err = json.Unmarshal(body, &locations) 115 | if err != nil { 116 | fmt.Printf("无法解析JSON: %v\n", err) 117 | return 118 | } 119 | // 读取位置数据并存入变量 120 | locationMap = make(map[string]location) 121 | for _, loc := range locations { 122 | locationMap[loc.Iata] = loc 123 | } 124 | // fmt.Println("读取到 loacations 机场位置数据") 125 | } 126 | } 127 | 128 | // 从文件中读取IP地址并处理 129 | func readIPResults(File string) ([]string, error) { 130 | file, err := os.Open(File) 131 | if err != nil { 132 | fmt.Println(err) 133 | return nil, err 134 | } 135 | defer file.Close() 136 | 137 | var ( 138 | ipPortWithTag []string 139 | ipPort string 140 | ipTag = strings.Split(File, ".")[0] 141 | ) 142 | 143 | scanner := bufio.NewScanner(file) 144 | for scanner.Scan() { 145 | ipAddr := scanner.Text() 146 | if len(ipAddr) < 7 || (strings.Count(ipAddr, ".") < 3 && strings.Count(ipAddr, ":") < 3) { 147 | continue 148 | } 149 | if strings.Count(ipAddr, "#") >= 1 { 150 | ipPort = strings.Split(ipAddr, "#")[0] 151 | } else if strings.Count(ipAddr, ",") >= 1 { 152 | ip := strings.Split(ipAddr, ",")[0] 153 | port := strings.Split(ipAddr, ",")[1] 154 | ipPort = net.JoinHostPort(ip, port) 155 | } 156 | 157 | if strings.Count(ipAddr, "丨") > 0 { 158 | ipTag = strings.Split(ipAddr, "丨")[1] 159 | } 160 | 161 | ipPortWithTag = append(ipPortWithTag, ipPort+"丨"+ipTag) 162 | } 163 | 164 | return ipPortWithTag, scanner.Err() 165 | } 166 | 167 | // 查询IP信息并返回其类型(住宅、商务、专线或其他) 168 | func getIPInfo(ip, apiKey string) (IPInfo, error) { 169 | var url string 170 | if apiKey != "" { 171 | url = fmt.Sprintf("https://api.ipapi.is?q=%s&key=%s", ip, apiKey) 172 | } else { 173 | fmt.Printf("\r使用免费接口,如检测量大请自行提供apiKey\r") 174 | url = fmt.Sprintf("https://api.ipapi.is/?ip=%s", ip) 175 | } 176 | 177 | // 创建一个自定义的 http.Client,并设置超时时间 178 | client := http.Client{ 179 | Timeout: 50 * time.Second, // 设置超时时间为10秒 180 | } 181 | 182 | // 使用自定义的 client 发送 GET 请求 183 | resp, err := client.Get(url) 184 | if err != nil { 185 | fmt.Print("x") 186 | return IPInfo{}, err 187 | } 188 | defer resp.Body.Close() 189 | 190 | body, err := io.ReadAll(resp.Body) 191 | if err != nil { 192 | fmt.Print("x") 193 | return IPInfo{}, err 194 | } 195 | 196 | var ipInfo IPInfo 197 | err = json.Unmarshal(body, &ipInfo) 198 | if err != nil { 199 | fmt.Print("x") 200 | return IPInfo{}, err 201 | } 202 | // fmt.Print(".") 203 | return ipInfo, err 204 | } 205 | 206 | // 为 IPInfo 结构体创建 getIPType 方法 207 | func (info *IPInfo) getIPType() (string, error) { 208 | if info.Company.Type == "isp" { 209 | switch info.ASN.Type { 210 | case "isp": 211 | return "住宅", nil 212 | case "business": 213 | return "家宽", nil 214 | case "hosting": 215 | return "托管", nil 216 | default: 217 | return info.ASN.Type, nil 218 | } 219 | } else if info.Company.Type == "business" { 220 | switch info.ASN.Type { 221 | case "isp": 222 | return "商宽", nil 223 | case "business": 224 | return "商务", nil 225 | case "hosting": 226 | return "VPS", nil 227 | default: 228 | return info.ASN.Type, nil 229 | } 230 | } else if info.Company.Type == "hosting" { 231 | switch info.ASN.Type { 232 | case "isp": 233 | return "中转", nil 234 | case "business": 235 | return "商管", nil 236 | case "hosting": 237 | return "机房", nil 238 | default: 239 | return "行业机房", nil 240 | } 241 | } else { 242 | return info.ASN.Type, nil 243 | } 244 | } 245 | 246 | // 原生ip还是广播ip 247 | func (info *IPInfo) isUniIP() bool { 248 | asnCountry := strings.ToUpper(info.ASN.Country) 249 | locCountry := strings.ToUpper(info.Location.Country_code) 250 | if asnCountry == locCountry { 251 | return true 252 | } 253 | return false 254 | } 255 | 256 | // Asn组织名称缩写 257 | func (info *IPInfo) getOrgNameAbbr() string { 258 | mappings := map[string]string{ 259 | "sk broadband": "SKB", 260 | "cmb": "CMB", 261 | "taegu": "CMB", 262 | "spectrum": "CFS", 263 | "cloudflare": "CF", 264 | "bigcommerce": "BigC", 265 | "tcloudnet": "TCN", 266 | "amazon": "AWS", 267 | "linked": "Lin", 268 | "porsche": "Porsche", 269 | "tencent": "Tencent", 270 | "alibaba": "ALi", 271 | "oracle": "Oracle", 272 | "powercomm": "LG", 273 | "powervis": "LG", 274 | "zdm network": "ZDM", 275 | "cogent": "Cog", 276 | "kirino": "Kirino", 277 | "microsoft": "Microsoft", 278 | "it7": "IT7", 279 | "cluster": "Cluster", 280 | "m247": "M247", 281 | "multacom": "MUL", 282 | "dimt": "DMIT", 283 | "chunghwa": "CHT", 284 | "pittqiao": "PIQ", 285 | } 286 | org := info.ASN.Org 287 | for key, value := range mappings { 288 | if strings.Contains(strings.ToLower(org), key) { 289 | return value 290 | } 291 | } 292 | 293 | if len(org) > 5 { 294 | return strings.ToUpper(org[:3]) 295 | } 296 | return strings.ToUpper(org) 297 | } 298 | 299 | func reParseResultTxtFile(ipFile string, apiKey string) { 300 | // 定义命令行参数 301 | flag.Parse() 302 | args := flag.Args() 303 | // ip := "8.8.8.8" // 替换为要查询的IP地址 304 | 305 | if len(args) > 0 { 306 | ipFile = args[0] 307 | } 308 | 309 | if ipFile == "" { 310 | ipFile = "ip-AI.txt" 311 | } 312 | 313 | ipPortWithTag, err := readIPResults(ipFile) 314 | if err != nil { 315 | return 316 | } 317 | type Result struct { 318 | index int 319 | line string 320 | } 321 | 322 | results := make(chan Result, len(ipPortWithTag)) 323 | 324 | var wg sync.WaitGroup 325 | fmt.Println("\033[90m正在查询ip信息并处理,请稍等!\033[0m") 326 | 327 | for i, ipPortWithTag := range ipPortWithTag { 328 | wg.Add(1) 329 | go func(index int, ipPortWithTag string) { 330 | defer func() { 331 | wg.Done() 332 | fmt.Print(".") 333 | }() 334 | 335 | ipPort := strings.Split(ipPortWithTag, "丨")[0] 336 | tag := strings.Split(ipPortWithTag, "丨")[1] 337 | var ip, port string 338 | if len(strings.Split(ipPort, ":")) <= 1 { 339 | ip = ipPort 340 | port = "443" 341 | } else { 342 | ip = strings.Split(ipPort, ":")[0] 343 | port = strings.Split(ipPort, ":")[1] 344 | } 345 | 346 | dataCenterCoCo := checkDataCenterCoco(ip, port) 347 | 348 | info, err := getIPInfo(ip, apiKey) 349 | if err != nil { 350 | results <- Result{index, fmt.Sprintf("获取ip信息错误 %s: %v", ip, err)} 351 | return 352 | } 353 | 354 | ipType, err := info.getIPType() 355 | if err != nil { 356 | results <- Result{index, fmt.Sprintf("获取ip类型错误 %s: %v", ip, err)} 357 | return 358 | } 359 | 360 | var uniIPStatus string 361 | if info.isUniIP() { 362 | uniIPStatus = "Uni" 363 | } else { 364 | uniIPStatus = "Bro" 365 | } 366 | 367 | // 根据数据中心地址和ip位置是否相同,设置显示信息 368 | var ipLocation string 369 | var cnProxy string 370 | ipCoCo := info.Location.Country_code 371 | 372 | ipLocation = ipCoCo 373 | if ipCoCo == dataCenterCoCo { 374 | ipLocation = ipCoCo 375 | } else if dataCenterCoCo != "" { 376 | ipLocation = ipCoCo + "-" + dataCenterCoCo 377 | if ipCoCo == "CN" { 378 | cnProxy = "中转" 379 | } 380 | } 381 | 382 | org := info.getOrgNameAbbr() 383 | line := fmt.Sprintf("%s:%s#%s-%s%s%s-%s-%d丨%s", ip, port, ipLocation, uniIPStatus, ipType, cnProxy, org, info.ASN.ASN, tag) 384 | 385 | results <- Result{index, line} 386 | }(i, ipPortWithTag) 387 | } 388 | 389 | go func() { 390 | wg.Wait() 391 | close(results) 392 | }() 393 | 394 | // 收集结果 395 | resultSlice := make([]string, len(ipPortWithTag)) 396 | for result := range results { 397 | resultSlice[result.index] = result.line 398 | } 399 | 400 | // 按顺序输出结果 401 | fmt.Println("\n\033[32m解析完成!\033[0m") 402 | fmt.Println("+--------------------------------------------------------+") 403 | for _, line := range resultSlice { 404 | fmt.Println(line) 405 | } 406 | fmt.Println("+--------------------------------------------------------+") 407 | } 408 | 409 | func checkDataCenterCoco(ip string, port string) string { 410 | client := http.Client{ 411 | Transport: &http.Transport{ 412 | // 使用 DialContext 函数 413 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 414 | return (&net.Dialer{}).DialContext(ctx, network, net.JoinHostPort(ip, port)) 415 | }, 416 | }, 417 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 418 | return http.ErrUseLastResponse // 阻止重定向 419 | }, 420 | Timeout: 30 * time.Second, 421 | } 422 | 423 | req, _ := http.NewRequest(http.MethodHead, requestURL, nil) 424 | 425 | // 添加用户代理 426 | 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") 427 | 428 | req.Close = true 429 | resp, err := client.Do(req) 430 | if err != nil { 431 | fmt.Print("x") 432 | return "" 433 | } 434 | if resp.StatusCode != http.StatusOK { 435 | fmt.Print("x") 436 | return "" 437 | } 438 | 439 | // 获取机场三字码,数据中心位置 440 | var colo string 441 | if resp.Header.Get("Server") == "cloudflare" { 442 | str := resp.Header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC 443 | colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str) 444 | } else { 445 | str := resp.Header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1 446 | colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str) 447 | } 448 | 449 | loc, ok := locationMap[colo] 450 | if ok { 451 | // fmt.Print(".") 452 | return loc.Cca2 453 | } 454 | fmt.Print("x") 455 | return "未获取数据" 456 | } 457 | 458 | // 检测当前apiKey是否达到上限 459 | func isApiKeyNotExceed(apiKey string) bool { 460 | url := fmt.Sprintf("https://api.ipapi.is?q=%s&key=%s", "8.8.8.8", apiKey) 461 | 462 | // 创建一个自定义的 http.Client,并设置超时时间 463 | client := http.Client{ 464 | Timeout: 50 * time.Second, // 设置超时时间为10秒 465 | } 466 | 467 | // 使用自定义的 client 发送 GET 请求 468 | resp, err := client.Get(url) 469 | if err != nil { 470 | fmt.Printf("连接错误:%v\n", err) 471 | return false 472 | } 473 | defer resp.Body.Close() 474 | 475 | body, err := io.ReadAll(resp.Body) 476 | if err != nil { 477 | fmt.Print("无法读取") 478 | return false 479 | } 480 | // fmt.Println(string(body)) 481 | if strings.Contains(string(body), "exceeded") { 482 | fmt.Println("达到限额") 483 | return false 484 | } 485 | return true 486 | } 487 | 488 | func main() { 489 | // apiKey := "" // 替换为你的API密钥 490 | apiKey := "085820d1fef7d000" 491 | if isApiKeyNotExceed(apiKey) { 492 | apiKey = "085820d1fef7d000" 493 | } else { 494 | apiKey = "7bfde515e7f4c845" 495 | } 496 | readLocationData() 497 | fmt.Print("\033[2J\033[0;0H") // 清空屏幕 498 | reParseResultTxtFile("", apiKey) 499 | } 500 | -------------------------------------------------------------------------------- /tool/tcpTest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | "regexp" 13 | "strings" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | type IPPort struct { 19 | IP string 20 | Port string 21 | } 22 | 23 | const ( 24 | timeout = 50 * time.Second // 连接超时时间 25 | maxDuration = 50 * time.Second // 请求和读取响应的最大持续时间 26 | requestURL = "https://speed.cloudflare.com/cdn-cgi/trace" // 请求trace URL 27 | // requestURL = "https://chatgpt.com/cdn-cgi/trace/" // 请求trace URL 28 | ) 29 | 30 | // checkIPPort 尝试连接到给定的IP和端口,并发送HTTP请求以获取响应内容 31 | func checkIPPort(ipPort IPPort, wg *sync.WaitGroup) string{ 32 | defer wg.Done() // 确保WaitGroup计数器在函数结束时递减 33 | 34 | // 设置连接超时的拨号器 35 | dialer := &net.Dialer{ 36 | Timeout: timeout, 37 | KeepAlive: 0, 38 | } 39 | 40 | // 创建一个带有超时的连接 41 | conn, err := dialer.DialContext(context.Background(), "tcp", net.JoinHostPort(ipPort.IP, ipPort.Port)) 42 | if err != nil { 43 | fmt.Printf("建立TCP连接错误 %s:%s - %s\n", ipPort.IP, ipPort.Port, err) 44 | return 45 | } 46 | defer conn.Close() 47 | 48 | // 设置HTTP客户端,使用自定义的拨号器 49 | client := http.Client{ 50 | Transport: &http.Transport{ 51 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 52 | return conn, nil 53 | }, 54 | // 为HTTPS设置TLS配置 55 | TLSClientConfig: &tls.Config{ 56 | InsecureSkipVerify: true, // 如果需要验证证书,请删除此行 57 | }, 58 | }, 59 | Timeout: maxDuration, 60 | } 61 | 62 | // 创建新的HTTP GET请求 63 | req, _ := http.NewRequest("GET", requestURL, nil) 64 | 65 | // 设置请求头中的用户代理 66 | req.Header.Set("User-Agent", "Mozilla/5.0") 67 | req.Close = true 68 | 69 | // 发送请求并获取响应 70 | resp, err := client.Do(req) 71 | if err != nil { 72 | fmt.Printf("访问错误 %s - %s\n", net.JoinHostPort(ipPort.IP, ipPort.Port), err) 73 | return 74 | } 75 | defer resp.Body.Close() 76 | 77 | // 将响应体内容复制到缓冲区 78 | var buf bytes.Buffer 79 | _, err = io.Copy(&buf, resp.Body) 80 | if err != nil { 81 | fmt.Printf("读取响应体错误: %v\n", err) 82 | return 83 | } 84 | 85 | content := buf.String() 86 | fmt.Printf("来自 %s 的响应内容:\n%s\n", net.JoinHostPort(ipPort.IP, ipPort.Port), content) 87 | if strings.Contains(content, "html") { 88 | fmt.Printf("来自 %s 的请求,成功\n", net.JoinHostPort(ipPort.IP, ipPort.Port)) 89 | } 90 | // 检查响应体是否包含指定的用户代理 91 | if strings.Contains(content, "uag=Mozilla/5.0") { 92 | // 使用正则表达式提取colo字段和loc字段 93 | coloRegex := regexp.MustCompile(`colo=([A-Z]+)`) 94 | locRegex := regexp.MustCompile(`loc=([A-Z]+)`) 95 | 96 | coloMatches := coloRegex.FindStringSubmatch(content) 97 | locMatches := locRegex.FindStringSubmatch(content) 98 | 99 | if len(coloMatches) > 1 && len(locMatches) > 1 { 100 | colo := coloMatches[1] 101 | loc := locMatches[1] 102 | fmt.Printf("IP %s:%s - colo: %s, loc: %s\n", ipPort.IP, ipPort.Port, colo, loc) 103 | } else { 104 | fmt.Printf("未找到 colo 或 loc 字段 - IP %s:%s\n", ipPort.IP, ipPort.Port) 105 | } 106 | } 107 | } 108 | 109 | func main() { 110 | // 定义命令行参数 111 | flag.Parse() 112 | args := flag.Args() 113 | 114 | // 默认的IP和端口 115 | defaultIPPorts := []IPPort{ 116 | {"8.8.8.8", "53"}, 117 | } 118 | 119 | var ipPorts []IPPort 120 | 121 | if len(args) == 0 { 122 | fmt.Println("未提供IP:Port参数,使用默认值") 123 | ipPorts = defaultIPPorts 124 | } else { 125 | // 解析输入的IP和端口列表 126 | for _, arg := range strings.Split(args[0], ",") { 127 | parts := strings.Split(arg, ":") 128 | if len(parts) == 9 { 129 | // ipv6格式:2a05:d014:ed:9600:f52b:ab01:6bb:bc9d 130 | ipv6 := strings.Join(parts[0:8], ":") 131 | port := parts[8] 132 | ipPorts = append(ipPorts, IPPort{ipv6, port}) 133 | } else if len(parts) == 2 { 134 | ipPorts = append(ipPorts, IPPort{IP: parts[0], Port: parts[1]}) 135 | } else { 136 | fmt.Printf("无效的IP:Port格式: %s\n", arg) 137 | continue 138 | } 139 | } 140 | } 141 | 142 | var wg sync.WaitGroup 143 | 144 | // 逐个检查IP和端口,使用goroutine实现并发 145 | for _, ipPort := range ipPorts { 146 | wg.Add(1) 147 | go checkIPPort(ipPort, &wg) 148 | } 149 | 150 | // 等待所有goroutine完成 151 | wg.Wait() 152 | } 153 | -------------------------------------------------------------------------------- /tool/tcpTestWithProxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "io" 8 | "net" 9 | "net/http" 10 | "net/url" 11 | // "os" 12 | // "strings" 13 | "time" 14 | 15 | "github.com/mattn/go-ieproxy" 16 | ) 17 | 18 | const ( 19 | timeout = 10 * time.Second // 连接超时时间 20 | requestURL = "https://chatgpt.com/cdn-cgi/trace/" // 请求trace URL 21 | ) 22 | 23 | var testIPs = []string{"1.1.1.1", "2.2.2.2"} 24 | 25 | func main() { 26 | for _, ip := range testIPs { 27 | fmt.Println(ip) 28 | checkAccess(ip) 29 | } 30 | } 31 | 32 | func checkAccess(testIP string) { 33 | // 解析URL以确定协议和端口 34 | parsedURL, err := url.Parse(requestURL) 35 | if err != nil { 36 | fmt.Println("无效的URL:", err) 37 | return 38 | } 39 | 40 | host := parsedURL.Hostname() 41 | port := parsedURL.Port() 42 | // fmt.Printf("host:%s,port:%s\n", host, port) 43 | if port == "" { 44 | // 如果未指定端口,基于协议设置默认端口 45 | switch parsedURL.Scheme { 46 | case "http": 47 | port = "80" 48 | case "https": 49 | port = "443" 50 | default: 51 | fmt.Println("未知协议:", parsedURL.Scheme) 52 | return 53 | } 54 | } 55 | 56 | // 设置连接超时的拨号器 57 | dialer := &net.Dialer{ 58 | Timeout: timeout, 59 | KeepAlive: 0, 60 | } 61 | host = testIP 62 | 63 | fmt.Printf("检测 host:%s,port:%s\n", host, port) 64 | // 创建一个带有超时的连接 65 | conn, err := dialer.DialContext(context.Background(), "tcp", net.JoinHostPort(host, port)) 66 | if err != nil { 67 | fmt.Printf("建立TCP连接错误 %s:%s - %s\n", host, port, err) 68 | return 69 | } 70 | defer conn.Close() 71 | 72 | proxyFunc := ieproxy.GetProxyFunc() 73 | // 设置HTTP客户端,使用自定义的拨号器 74 | client := http.Client{ 75 | Transport: &http.Transport{ 76 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 77 | // 仅在首次连接时使用已建立的TCP连接 78 | if addr == net.JoinHostPort(host, port) { 79 | return conn, nil 80 | } 81 | return dialer.DialContext(ctx, network, addr) 82 | }, 83 | Proxy: proxyFunc, 84 | // 为HTTPS设置TLS配置 85 | TLSClientConfig: &tls.Config{ 86 | InsecureSkipVerify: true, // 如果需要验证证书,请删除此行 87 | }, 88 | }, 89 | Timeout: 10 * time.Second, 90 | } 91 | 92 | // 创建新的HTTP GET请求 93 | req, _ := http.NewRequest("GET", requestURL, nil) 94 | 95 | // 设置请求头中的用户代理 96 | req.Header.Set("User-Agent", "Mozilla/5.0") 97 | req.Close = true 98 | 99 | // 发送请求并获取响应 100 | resp, err := client.Do(req) 101 | if err != nil { 102 | fmt.Printf("访问错误 %s:%s - %s\n", host, port, err) 103 | return 104 | } 105 | defer resp.Body.Close() 106 | 107 | // 读取响应内容 108 | body, err := io.ReadAll(resp.Body) 109 | if err != nil { 110 | fmt.Println("读取响应错误:", err) 111 | return 112 | } 113 | 114 | // 打印响应状态码和内容 115 | fmt.Println("响应状态码:", resp.StatusCode) 116 | fmt.Println("响应内容:\n", string(body)) 117 | 118 | // // 将响应内容保存到以IP地址命名的文件中 119 | // fileName := fmt.Sprintf("%s.html", testIP) 120 | // err = os.WriteFile(fileName, body, 0o644) 121 | // if err != nil { 122 | // fmt.Println("写入文件错误:", err) 123 | // return 124 | // } 125 | // if strings.Contains(string(body), "Mozilla/5.0"){ 126 | // fmt.Println("访问成功") 127 | // } 128 | } 129 | --------------------------------------------------------------------------------