[
  {
    "path": ".github/workflows/go.yml",
    "content": "# This workflow will build a golang project\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go\n\nname: Go\n\non:\n  push:\n    branches: [\"master\"]\n  pull_request:\n    branches: [\"master\"]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up Go\n        uses: actions/setup-go@v4\n        with:\n          go-version: \"1.20\"\n\n      - name: Install dependencies\n        run: go get .\n\n      - name: Build\n        run: go build -v ./...\n\n      - name: Test\n        run: go test -v ./...\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: goreleaser\n\non:\n  push:\n    # run only against tags\n    tags:\n      - \"*\"\n\npermissions:\n  contents: write\n  # packages: write\n  # issues: write\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n      - run: git fetch --force --tags\n      - uses: actions/setup-go@v4\n        with:\n          go-version: \"1.20\"\n      # More assembly might be required: Docker logins, GPG, etc. It all depends\n      # on your needs.\n      - uses: goreleaser/goreleaser-action@v4\n        with:\n          # either 'goreleaser' (default) or 'goreleaser-pro':\n          distribution: goreleaser\n          version: latest\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          # Your GoReleaser Pro key, if you are using the 'goreleaser-pro'\n          # distribution:\n          # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n*.zip\n*.7z\n\nout*.txt\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736\n.glide/\ngscan_quic/\nbackup/\nbin/\n"
  },
  {
    "path": ".goreleaser.yaml",
    "content": "archives:\n  - id: \"gscan_quic\"\n    format_overrides:\n      - goos: windows\n        format: zip\n    files:\n      - LICENSE\n      - README.md\n      - config.json\n      - \"*.example\"\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\n\ngo:\n- \"1.10\"\n\nbefore_install:\n- go get github.com/Kisesy/gscan_quic\n- sudo apt-get install p7zip-full\n\nscript:\n- export CGO_ENABLED=0\n- export EXE=gscan_quic\n- export TAR_EXCLUDE=\"--exclude=*.go --exclude=*.bz2 --exclude=*.7z --exclude=*.xz --exclude=vendor\"\n\n- echo ${TRAVIS_TAG} > Version.txt\n\n- export GOOS=windows GOARCH=386 && go build -ldflags \"-w -s\"\n- 7za a ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.7z -ms=on -mx=9 -r- -mmt -x'!*.go' -x'!*.7z' -x'!*.zip' -x'!vendor/' *\n- rm *.exe\n- export GOOS=windows GOARCH=amd64 && go build -ldflags \"-w -s\"\n- 7za a ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.7z -ms=on -mx=9 -r- -mmt -x'!*.go' -x'!*.7z' -x'!*.zip' -x'!vendor/' *\n- rm *.exe\n\n- export GOOS=linux GOARCH=386 XZ_OPT=-9 && go build -ldflags \"-w -s\"\n- tar cvJpf ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.tar.xz * ${TAR_EXCLUDE}\n- export GOOS=linux GOARCH=amd64 XZ_OPT=-9 && go build -ldflags \"-w -s\"\n- tar cvJpf ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.tar.xz * ${TAR_EXCLUDE}\n\n- export GOOS=linux GOARCH=arm && go build -ldflags \"-w -s\"\n- tar cjf ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.tar.bz2 * ${TAR_EXCLUDE}\n- export GOOS=darwin GOARCH=amd64 && go build -ldflags \"-w -s\"\n- tar cjf ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.tar.bz2 * ${TAR_EXCLUDE}\n\n- md5sum *.7z *.xz *.bz2 | tee md5sum\n- sha1sum *.7z *.xz *.bz2 | tee sha1sum\n\ndeploy:\n  provider: releases\n  api_key:\n    secure: ${TOKEN}\n  file_glob: true\n  file: \n    - \"*.7z\"\n    - \"*.xz\"\n    - \"*.bz2\"\n    - \"md5sum\"\n    - \"sha1sum\"\n  skip_cleanup: true\n  on:\n    tags: true\n\nnotifications:\n  email: false"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>\n"
  },
  {
    "path": "README.md",
    "content": "\r\n# gscan-quic\r\n\r\n一个 IP 可用性扫描工具\r\n\r\n## 当前支持\r\n\r\n- [x] QUIC\r\n- [x] SNI\r\n- [x] TLS\r\n- [x] PING\r\n- [ ] SOCKS4/SOCKS4A/SOCKS5\r\n\r\n## 简单说明\r\n\r\n因为支持多种类型的IP扫描, 所以 IP 段文件是分开放的.\r\n\r\n比如 quic 的IP段文件是 iprange_quic.txt, tls 的就是 iprange_tls.txt\r\n\r\n其他类型的都可以在 config.json 文件里看到\r\n\r\n**IP段文件格式如下：**\r\n\r\n    # 下面几种都是支持的, 遇到错误IP格式是会跳过的\r\n\r\n    1.9.23.0            \r\n    1.9.23.0/24\r\n    1.9.0.0/16\r\n    \r\n    1.9.22.0-1.9.23.0/24\r\n    \r\n    1.9.22.0/24-1.9.33.0/24\r\n    \r\n    1.9.22.0-255\r\n    1.9.22.0-1.9.22.0\r\n    1.9.22.0-1.9.22.255\r\n    1.9.22.0-1.9.33.255\r\n\r\n    1.9.22.111-1.9.22.111    会自动精简成 1.9.22.111\r\n    1.9.22.0/24-1.9.22.0/24  会自动精简成 1.9.22.0/24\r\n\r\n    # 支持 ipv6 格式\r\n\r\n    2001:db8::1\r\n    2001:db8::1/128\r\n\r\n    # 支持 gop 的 \"xxx\",\"xxx\" 和 goa 的 xxx|xxx 格式\r\n\r\n    \"1.9.22.0\", \"1.9.22.1\",\"1.9.22.2\",\r\n    1.9.22.0|1.9.22.1|\r\n\r\n    # IP段也是会自动去重的\r\n\r\n    1.9.22.0-255\r\n    1.9.0.0/16\r\n    1.9.22.0-255\r\n    1.9.22.0/24\r\n    1.9.22.0-255\r\n    1.9.22.0-1.9.22.100\r\n    1.9.22.0-1.9.22.255\r\n    1.9.0.0/16\r\n    3.3.3.0/24\r\n    3.3.0.0/16\r\n    3.3.3.0-255\r\n    1.1.1.0/24\r\n    1.9.0.0/16\r\n    # 上面几个经过去重只会留下\r\n    3.3.0.0/16\r\n    1.9.0.0/16\r\n    1.1.1.0/24\r\n\r\n> **注意:**\r\n\r\n* 默认是有输出个数限制的, 可以设置配置文件里的 RecordLimit\r\n\r\n* 在扫描过程中是可以中断的, 只要按 <kbd>Ctrl</kbd>+<kbd>C</kbd> 就可以中断, 扫过的IP是会保留的\r\n\r\n* 扫描IP段是随机的\r\n\r\n* 如果IP段是 xx|xx 或 \"xxx\",\"xxx\" 格式的, 那么一行的字节加起来大小不能超过4MB, 如有超过, 必须分行, 否则会跳过这一行\r\n\r\n\r\n## 配置说明\r\n\r\n参考 config.json 文件\r\n\r\n每次更新时最好覆盖掉 config.json 文件\r\n\r\n支持 gop 形式的个人配置 config.user.json, 所以为了防止每次修改, 可以自己新建一个.\r\n\r\n## 下载\r\n到 https://github.com/Kisesy/gscan_quic/releases 下载编译好的\r\n\r\n## 感谢\r\n\r\n改自 yinqiwen 大神的 https://github.com/yinqiwen/gscan 在此感谢\r\n"
  },
  {
    "path": "config.json",
    "content": "{\n\t// 注意:\n\t// [扫描并发数] 理论上设置越大扫的越快, 但是并不意味越大就可以扫到更多IP\n\t// 大量的并发, 会造成网络堵塞, 甚至触到系统网络的限制或造成路由器宕机而出现更多的问题\n\t// 所以如果在你那里基本扫不到IP, 可以试着减小扫描并发数并增大超时时间\n\n\t// 新添了 Level 设置\n\t// 可以按照说明进行修改来达到加速扫描的效果, 但是为了结果的准确性最好还是设置为最高\n\n\t// 扫描并发数\n\t\"ScanWorker\": 100,\n\n\t// 是否启用 Ping, 每次扫描 Tls、Quic...前都会先 ping 一下\n\t\"VerifyPing\": false,\n\t// Ping 的参数\n\t\"ScanMinPingRTT\": 80,\n\t\"ScanMaxPingRTT\": 800,\n\n\t// 是否开启备份\n\t// 每次扫到的IP，都会在此目录下备份一份\n\t\"EnableBackup\": true,\n\t\"BackupDir\": \"./backup\",\n\t\n\t// 是否禁用结束扫描时的命令行暂停\n\t\"DisablePause\": false,\n\n\t// 扫描方式, 可以设置为下面的任意一个, 大小写都可以\n\t\"ScanMode\": \"quic\",\n\n\t// 如果设置为 ping, VerifyPing 会自动关闭\n\t\"Ping\": {\n\t\t\"ScanCountPerIP\": 1,\n\t\t\"ScanMinRTT\": 0,\n\t\t\"ScanMaxRTT\": 1000,\n\t\t\"RecordLimit\": 10000,\n\t\t\"OutputSeparator\": \"\\r\\n\",\n\t\t// 支持带端口格式的IP, 只是为了使用方便, 并不是真的支持端口, 端口会被自动去除\n\t\t\"InputFile\": \"./iprange_ping.txt\",\n\t\t\"OutputFile\": \"./out_ping.txt\",\n\t},\n\n\t\"QUIC\": {\n\t\t// 每个IP的测试次数\n\t\t\"ScanCountPerIP\": 1,\n\t\t// ServerName 可以设置多项, 会随机选择一个\n\t\t// 默认空列表，随机生成域名格式字符串\n\t\t// 如果要设置为空, 可以写为 [\"\"]\n\t\t\"ServerName\": [],\n\t\t// HTTPVerifyHosts 是 http 测试时使用的测试地址\n\t\t// 如果设置多个, 将会随机选择一个进行测试\n\t\t\"HTTPVerifyHosts\": [\"dns.google.com\"],\n\t\t// 握手超时时间, 单位: 毫秒\n\t\t\"HandshakeTimeout\": 2500,\n\t\t\"ScanMinRTT\": 0,\n\t\t\"ScanMaxRTT\": 3000,\n\t\t// 结果的限制数量, 比如: 设置成 100, 将会扫到100个IP后自动结束\n\t\t\"RecordLimit\": 10000,\n\t\t// 输出结果的分隔符, 比如: 如果想要换行输出, 可以改为: \\n\n\t\t// 有个特殊的例外, 如果设置为 gop, 则会输出 \"xxx\", \"xxx\" 样式\n\t\t\"OutputSeparator\": \"gop\",\n\t\t// IP 或 IP 段文件\n\t\t\"InputFile\": \"./iprange_quic.txt\",\n\t\t// 输出的文件路径\n\t\t\"OutputFile\": \"./out_quic.txt\",\n\t\t// 验证等级\n\t\t// 1: 只是测试连接成功, 并且确定证书存在\n\t\t// 2: 验证证书是否正确\n\t\t// 3: 测试 HTTP 连接\n\t\t// 4: 验证是否是 NoSuchBucket 错误\n\t\t// (2.x版默认等级为3, 所以如果lv2搜到的IP不能用, 可以改为 3)\n\t\t\"Level\": 4,\n\t},\n\n\t// 暂时只支持 google IP\n\t\"TLS\": {\n\t\t// 同 QUIC 说明\n\t\t\"ScanCountPerIP\": 1,\n\t\t\"ServerName\": [],\n\t\t\"HTTPVerifyHosts\": [\"dns.google.com\"],\n\t\t\"HandshakeTimeout\": 2500,\n\t\t\"ScanMinRTT\": 0,\n\t\t\"ScanMaxRTT\": 3000,\n\t\t\"RecordLimit\": 10000,\n\t\t\"OutputSeparator\": \"|\",\n\t\t\"InputFile\": \"./iprange_tls.txt\",\n\t\t\"OutputFile\": \"./out_tls.txt\",\n\t\t// 1: 测试 tls 连接并握手成功\n\t\t// 2: 证书验证\n\t\t// 3: http 测试\n\t\t\"Level\": 3,\n\t},\n\n\t\"SNI\": {\n\t\t\"ScanCountPerIP\": 1,\n\t\t// 注意, SNI 需要同时验证两个不同域名的 ServerName 才能正确确认\n\t\t// 当然你也可以改成一个来简单确认\n\t\t// 还有, 不要填封锁严重的域名, 比如 google 的域名\n\t\t// 这里的域名最好不用默认的, 防止检测\n\t\t\"ServerName\": [\"www.bing.com\", \"duckduckgo.com\"],\n\t\t// \"HTTPVerifyHosts\": [\"dns.google.com\"],\n\t\t\"HandshakeTimeout\": 2500,\n\t\t\"ScanMinRTT\": 0,\n\t\t\"ScanMaxRTT\": 3000,\n\t\t\"RecordLimit\": 10000,\n\t\t\"OutputSeparator\": \"\\r\\n\",\n\t\t\"InputFile\": \"./iprange_sni.txt\",\n\t\t\"OutputFile\": \"./out_sni.txt\",\n\t\t// 1: 测试 tls 连接并握手成功\n\t\t// 2: 证书验证\n\t\t// 3: http 测试\n\t\t\"Level\": 2,\n\t},\n}\n"
  },
  {
    "path": "config.user.json.example",
    "content": "{\n\t\"ScanWorker\": 100,\n\t\"VerifyPing\": false,\n\t\"ScanMinPingRTT\": 80,\n\t\"ScanMaxPingRTT\": 800,\t\n\t\n\t\"ScanMode\":   \"quic\",\n\t\n\t\"Ping\": {\n\t\t//\"ScanCountPerIP\":  1,\n\t\t//\"ScanMinRTT\":      0,\n\t\t//\"ScanMaxRTT\":      1000,\n\t\t//\"RecordLimit\":     10000,\n\t\t//\"OutputSeparator\": \"\\r\\n\",\n\t\t//\"InputFile\":       \"./iprange_ping.txt\",\n\t\t//\"OutputFile\":      \"./out_ping.txt\"\n\t},\n\n\t\"QUIC\": {\n\t\t//\"ScanCountPerIP\":   1,\n\t\t//\"ServerName\":       [],\n\t\t//\"HTTPVerifyHosts\":  [\"dns.google.com\"],\n\t\t//\"HandshakeTimeout\": 2500,\n\t\t//\"ScanMinRTT\":       0,\n\t\t//\"ScanMaxRTT\":       3000,\n\t\t//\"RecordLimit\":      10000,\n\t\t//\"OutputSeparator\":  \"gop\",\n\t\t//\"InputFile\": \"./iprange_quic.txt\",\n\t\t//\"OutputFile\": \"./out_quic.txt\",\n\t\t//\"Level\": 3\n\t},\n\n\t\"TLS\": {\n\t\t//\"ScanCountPerIP\":   1,\n\t\t//\"ServerName\":       [],\n\t\t//\"HTTPVerifyHosts\":  [\"dns.google.com\"],\n\t\t//\"HandshakeTimeout\": 2500,\n\t\t//\"ScanMinRTT\":       0,\n\t\t//\"ScanMaxRTT\":       3000,\n\t\t//\"RecordLimit\":      10000,\n\t\t//\"OutputSeparator\":  \"|\",\n\t\t//\"InputFile\":        \"./iprange_tls.txt\",\n\t\t//\"OutputFile\":       \"./out_tls.txt\",\n\t\t//\"Level\": 3\n\t},\n\n\t\"SNI\": {\n\t\t//\"ScanCountPerIP\": 1,\n\t\t//\"ServerName\": [\"www.bing.com\", \"duckduckgo.com\"],\n\t\t//\"HandshakeTimeout\": 2500,\n\t\t//\"ScanMinRTT\":       0,\n\t\t//\"ScanMaxRTT\":       3000,\n\t\t//\"RecordLimit\":      10000,\n\t\t//\"OutputSeparator\":  \"\\r\\n\",\n\t\t//\"InputFile\":        \"./iprange_sni.txt\",\n\t\t//\"OutputFile\":       \"./out_sni.txt\",\n\t\t//\"Level\": 2\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/kisesy/gscan_quic\n\ngo 1.21\n\nrequire (\n\tgithub.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721\n\tgithub.com/quic-go/quic-go v0.36.1-0.20230701190300-fd0c9bbf9e1f\n)\n\nrequire (\n\tgithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect\n\tgithub.com/golang/mock v1.6.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect\n\tgithub.com/onsi/ginkgo/v2 v2.9.5 // indirect\n\tgithub.com/quic-go/qpack v0.4.0 // indirect\n\tgithub.com/quic-go/qtls-go1-20 v0.3.0 // indirect\n\tgolang.org/x/crypto v0.4.0 // indirect\n\tgolang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect\n\tgolang.org/x/mod v0.10.0 // indirect\n\tgolang.org/x/net v0.10.0 // indirect\n\tgolang.org/x/sys v0.8.0 // indirect\n\tgolang.org/x/text v0.9.0 // indirect\n\tgolang.org/x/tools v0.9.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=\ngithub.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=\ngithub.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=\ngithub.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=\ngithub.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=\ngithub.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=\ngithub.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=\ngithub.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=\ngithub.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=\ngithub.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=\ngithub.com/quic-go/qtls-go1-20 v0.3.0 h1:NrCXmDl8BddZwO67vlvEpBTwT89bJfKYygxv4HQvuDk=\ngithub.com/quic-go/qtls-go1-20 v0.3.0/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=\ngithub.com/quic-go/quic-go v0.36.1-0.20230701190300-fd0c9bbf9e1f h1:I4cT4zLUjYfL6SEqIzZQWe1xTmJRvKZRWrLhmcyaZeA=\ngithub.com/quic-go/quic-go v0.36.1-0.20230701190300-fd0c9bbf9e1f/go.mod h1:XtCUOCALTTWbPyd0IxFfHf6h0sEMubRFvEYHl3QxKw8=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=\ngolang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=\ngolang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=\ngolang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=\ngolang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=\ngolang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "gscan.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype ScanConfig struct {\n\tScanCountPerIP   int\n\tServerName       []string\n\tHTTPVerifyHosts  []string\n\tHandshakeTimeout time.Duration\n\tScanMinRTT       time.Duration\n\tScanMaxRTT       time.Duration\n\tRecordLimit      int\n\tInputFile        string\n\tOutputFile       string\n\tOutputSeparator  string\n\tLevel            int\n}\n\ntype GScanner struct {\n\tScanWorker     int\n\tVerifyPing     bool\n\tScanMinPingRTT time.Duration\n\tScanMaxPingRTT time.Duration\n\tDisablePause   bool\n\tEnableBackup   bool\n\tBackupDir      string\n\n\tScanRecords `json:\"-\"`\n\n\tScanMode string\n\tPING     ScanConfig\n\tQUIC     ScanConfig\n\tTLS      ScanConfig\n\tSNI      ScanConfig\n}\n\nfunc init() {\n\trand.Seed(time.Now().UnixNano())\n\n\tlog.SetFlags(log.LstdFlags | log.Lshortfile)\n}\n\nfunc (gs *GScanner) loadConfig(cfgFile string) error {\n\texe, err := os.Executable()\n\tif err != nil {\n\t\treturn errors.New(\"could not get executable path\")\n\t}\n\texecFolder := filepath.Dir(exe)\n\n\tif strings.HasPrefix(cfgFile, \"./\") {\n\t\tcfgFile = filepath.Join(execFolder, cfgFile)\n\t}\n\n\tconfig := gs\n\tif err := readJsonConfig(cfgFile, config); err != nil {\n\t\treturn fmt.Errorf(\"could not read config file: %v\", err)\n\t}\n\n\tif config.EnableBackup {\n\t\tif strings.HasPrefix(config.BackupDir, \"./\") {\n\t\t\tconfig.BackupDir = filepath.Join(execFolder, config.BackupDir)\n\t\t}\n\t\terr := os.MkdirAll(config.BackupDir, 0o644)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not create backup dir: %v\", err)\n\t\t}\n\t}\n\n\tconfig.ScanMode = strings.ToLower(config.ScanMode)\n\tif config.ScanMode == \"ping\" {\n\t\tconfig.VerifyPing = false\n\t}\n\n\tconfig.ScanMinPingRTT *= time.Millisecond\n\tconfig.ScanMaxPingRTT *= time.Millisecond\n\n\tscanConfigs := []*ScanConfig{&config.QUIC, &config.TLS, &config.SNI, &config.PING}\n\tfor _, scanConfig := range scanConfigs {\n\t\tif strings.HasPrefix(scanConfig.InputFile, \"./\") {\n\t\t\tscanConfig.InputFile = filepath.Join(execFolder, scanConfig.InputFile)\n\t\t} else {\n\t\t\tscanConfig.InputFile, _ = filepath.Abs(scanConfig.InputFile)\n\t\t}\n\t\tif strings.HasPrefix(scanConfig.OutputFile, \"./\") {\n\t\t\tscanConfig.OutputFile = filepath.Join(execFolder, scanConfig.OutputFile)\n\t\t} else {\n\t\t\tscanConfig.OutputFile, _ = filepath.Abs(scanConfig.OutputFile)\n\t\t}\n\t\tif !pathExist(scanConfig.InputFile) {\n\t\t\tos.Create(scanConfig.InputFile)\n\t\t}\n\n\t\tscanConfig.ScanMinRTT *= time.Millisecond\n\t\tscanConfig.ScanMaxRTT *= time.Millisecond\n\t\tscanConfig.HandshakeTimeout *= time.Millisecond\n\t}\n\treturn nil\n}\n\nfunc main() {\n\tvar cfgfile string\n\tflag.StringVar(&cfgfile, \"Config File\", \"./config.json\", \"Config file, json format\")\n\tflag.Parse()\n\n\tscanner := new(GScanner)\n\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tfmt.Println(\"panic:\", r)\n\t\t}\n\t\tfmt.Println()\n\t\tif scanner.DisablePause {\n\t\t\treturn\n\t\t}\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tcmd := exec.Command(\"cmd\", \"/C\", \"pause\")\n\t\t\tcmd.Stdout = os.Stdout\n\t\t\tcmd.Stdin = os.Stdin\n\t\t\t// 改为 start, 程序可以正常退出, 这样一些程序监视工具可以正常测到程序结束了\n\t\t\tcmd.Start()\n\t\t} else {\n\t\t\tfmt.Println(\"Press [Enter] to exit...\")\n\t\t\tfmt.Scanln()\n\t\t}\n\t}()\n\terr := scanner.loadConfig(cfgfile)\n\tif err != nil {\n\t\tlog.Println(err)\n\t\treturn\n\t}\n\n\tscanMode := scanner.ScanMode\n\tcfg, _ := scanner.getScanConfig(scanMode)\n\n\tiprangeFile := cfg.InputFile\n\tif !pathExist(iprangeFile) {\n\t\tlog.Panicf(\"IP Range file not exist: %s\", iprangeFile)\n\t}\n\n\tlog.Printf(\"Start loading IP Range file: %s\", iprangeFile)\n\tipqueue, err := parseIPRangeFile(iprangeFile)\n\tif err != nil {\n\t\tlog.Panicln(err)\n\t}\n\n\tlog.Printf(\"Start scanning available IP\")\n\tstartTime := time.Now()\n\tscanner.StartScan(ipqueue)\n\n\trecords := scanner.Records()\n\n\tlog.Printf(\"Scanned %d IP in %s, found %d records\",\n\t\tscanner.ScanCount(), time.Since(startTime), len(records))\n\n\tif len(records) == 0 {\n\t\treturn\n\t}\n\n\tsort.Slice(records, func(i, j int) bool {\n\t\treturn records[i].RTT < records[j].RTT\n\t})\n\ta := make([]string, len(records))\n\tfor i, r := range records {\n\t\ta[i] = r.IP\n\t}\n\tb := new(bytes.Buffer)\n\tif cfg.OutputSeparator == \"gop\" {\n\t\tout := strings.Join(a, `\", \"`)\n\t\tb.WriteString(`\"`)\n\t\tb.WriteString(out)\n\t\tb.WriteString(`\",`)\n\t} else {\n\t\tout := strings.Join(a, cfg.OutputSeparator)\n\t\tb.WriteString(out)\n\t}\n\n\tif err := os.WriteFile(cfg.OutputFile, b.Bytes(), 0o644); err != nil {\n\t\tlog.Printf(\"Failed to write output file:%s for reason: %v\", cfg.OutputFile, err)\n\t} else {\n\t\tlog.Printf(\"All results written to %s\", cfg.OutputFile)\n\t}\n\n\tif scanner.EnableBackup {\n\t\tfilename := fmt.Sprintf(\"%s_%s_lv%d.txt\", scanMode, time.Now().Format(\"20060102_150405\"), cfg.Level)\n\n\t\tbakfilename := filepath.Join(scanner.BackupDir, filename)\n\t\tif err := os.WriteFile(bakfilename, b.Bytes(), 0o644); err != nil {\n\t\t\tlog.Printf(\"Failed to write output file:%s for reason: %v\\n\", bakfilename, err)\n\t\t} else {\n\t\t\tlog.Printf(\"All results written to %s\\n\", bakfilename)\n\t\t}\n\t}\n}\n\nfunc (gcfg *GScanner) getScanConfig(scanMode string) (*ScanConfig, testIPFunc) {\n\tswitch scanMode {\n\tcase \"quic\":\n\t\treturn &gcfg.QUIC, testQuic\n\tcase \"tls\":\n\t\treturn &gcfg.TLS, testTls\n\tcase \"sni\":\n\t\treturn &gcfg.SNI, testSni\n\tcase \"ping\":\n\t\treturn &gcfg.PING, testPing\n\t// case \"socks5\":\n\t// testIPFunc = testSocks5\n\tdefault:\n\t\tlog.Panicln(\"Unknown scan mode:\", scanMode)\n\t}\n\treturn nil, nil\n}\n"
  },
  {
    "path": "iprange.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/mikioh/ipaddr\"\n)\n\n// 从每组地址中分离出起始IP以及结束IP\n// 参考自 moonshawdo 的代码\nfunc splitIP(strline string) []ipaddr.Prefix {\n\tvar begin, end string\n\tif strings.Contains(strline, \"-\") && strings.Contains(strline, \"/\") {\n\t\tss := strings.Split(strline, \"-\")\n\t\tif len(ss) == 2 {\n\t\t\tiprange1, iprange2 := ss[0], ss[1]\n\t\t\t// \"1.9.22.0/24-1.9.22.0\"\n\t\t\tif strings.Contains(iprange1, \"/\") && !strings.Contains(iprange2, \"/\") {\n\t\t\t\tbegin = iprange1[:strings.Index(iprange1, \"/\")]\n\t\t\t\tif begin == iprange2 {\n\t\t\t\t\tiprange2 = iprange1\n\t\t\t\t}\n\t\t\t} else if strings.Contains(iprange2, \"/\") {\n\t\t\t\t// 1.9.22.0/24-1.9.22.0/24\n\t\t\t\tbegin = iprange1[:strings.Index(iprange1, \"/\")]\n\t\t\t} else {\n\t\t\t\t// 1.9.22.0-1.9.23.0/24\n\t\t\t\tbegin = iprange1\n\t\t\t}\n\t\t\t// c, err := ipaddr.Parse(begin + \",\" + iprange2)\n\t\t\t// if err != nil {\n\t\t\t// \tpanic(err)\n\t\t\t// }\n\t\t\t// return ipaddr.Aggregate(c.List())\n\n\t\t\tif c, err := ipaddr.Parse(iprange2); err == nil {\n\t\t\t\tend = c.Last().IP.String()\n\t\t\t}\n\t\t}\n\t} else if strings.Contains(strline, \"-\") {\n\t\tnum_regions := strings.Split(strline, \".\")\n\t\tif len(num_regions) == 4 {\n\t\t\t// \"xxx.xxx.xxx-xxx.xxx-xxx\"\n\t\t\tfor _, region := range num_regions {\n\t\t\t\tif strings.Contains(region, \"-\") {\n\t\t\t\t\ta := strings.Split(region, \"-\")\n\t\t\t\t\ts, e := a[0], a[1]\n\t\t\t\t\tbegin += \".\" + s\n\t\t\t\t\tend += \".\" + e\n\t\t\t\t} else {\n\t\t\t\t\tbegin += \".\" + region\n\t\t\t\t\tend += \".\" + region\n\t\t\t\t}\n\t\t\t}\n\t\t\tbegin = begin[1:]\n\t\t\tend = end[1:]\n\t\t} else {\n\t\t\t// \"xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx\"\n\t\t\ta := strings.Split(strline, \"-\")\n\t\t\tbegin, end = a[0], a[1]\n\t\t\tif 1 <= len(end) && len(end) <= 3 {\n\t\t\t\tprefix := begin[0:strings.LastIndex(begin, \".\")]\n\t\t\t\tend = prefix + \".\" + end\n\t\t\t}\n\t\t}\n\t} else if strings.HasSuffix(strline, \".\") {\n\t\t// \"xxx.xxx.xxx.\"\n\t\tbegin = strline + \"0\"\n\t\tend = strline + \"255\"\n\t} else if strings.Contains(strline, \"/\") {\n\t\t// \"xxx.xxx.xxx.xxx/xx\"\n\t\tif c, err := ipaddr.Parse(strline); err == nil {\n\t\t\treturn c.List()\n\t\t}\n\t\treturn nil\n\t} else {\n\t\t// \"xxx.xxx.xxx.xxx\"\n\t\t// 如果IP带有端口, 那么就分离端口\n\t\tif i := strings.LastIndex(strline, \":\"); i != -1 {\n\t\t\tif c, err := ipaddr.Parse(strline[:i]); err == nil {\n\t\t\t\treturn c.List()\n\t\t\t}\n\t\t}\n\t\tbegin = strline\n\t\tend = strline\n\t}\n\n\treturn ipaddr.Summarize(net.ParseIP(begin), net.ParseIP(end))\n}\n\nvar sepReplacer = strings.NewReplacer(`\",\"`, \",\", `\", \"`, \",\", \"|\", \",\")\n\nfunc parseIPRangeFile(file string) (chan string, error) {\n\tf, err := os.Open(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\n\tipranges := make([]ipaddr.Prefix, 0)\n\tscanner := bufio.NewScanner(f)\n\t// 一行最大 4MB\n\tbuf := make([]byte, 1024*1024*4)\n\tscanner.Buffer(buf, len(buf))\n\n\tfor scanner.Scan() {\n\t\tline := strings.TrimFunc(scanner.Text(), func(r rune) bool {\n\t\t\tswitch r {\n\t\t\tcase ',', '|', '\"', '\\'':\n\t\t\t\treturn true\n\t\t\tcase '\\t', '\\n', '\\v', '\\f', '\\r', ' ', 0x85, 0xA0:\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t})\n\t\tif line == \"\" || strings.HasPrefix(line, \"#\") {\n\t\t\tcontinue\n\t\t}\n\n\t\t// 支持 gop 的 \"xxx\",\"xxx\" 和 goa 的 xxx|xxx 格式\n\t\tif s := sepReplacer.Replace(line); strings.Contains(s, \",\") {\n\t\t\tif c, err := ipaddr.Parse(s); err == nil {\n\t\t\t\tipranges = append(ipranges, c.List()...)\n\t\t\t}\n\t\t} else {\n\t\t\tipranges = append(ipranges, splitIP(line)...)\n\t\t}\n\t}\n\n\tout := make(chan string, 200)\n\tgo func() {\n\t\tdefer close(out)\n\t\tif len(ipranges) > 0 {\n\t\t\tipranges = dedup(ipranges)\n\n\t\t\t// 打乱IP段扫描顺序\n\t\t\trand.Shuffle(len(ipranges), func(i, j int) {\n\t\t\t\tipranges[i], ipranges[j] = ipranges[j], ipranges[i]\n\t\t\t})\n\n\t\t\t// 打乱IP的扫描顺序, 不再等待一个IP段扫描完毕再进行下一个, 提高扫描的随机性\n\t\t\t// 有时候可以更快的扫到IP\n\t\t\tn := 15\n\t\t\tif len(ipranges) < n {\n\t\t\t\tn = len(ipranges)\n\t\t\t}\n\t\t\tops(len(ipranges), n, func(i, _ int) {\n\t\t\t\tc := ipaddr.NewCursor([]ipaddr.Prefix{ipranges[i]})\n\t\t\t\tfor ip := c.First(); ip != nil; ip = c.Next() {\n\t\t\t\t\tout <- ip.IP.String()\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}()\n\treturn out, nil\n}\n\n/*\nIP段去重\t(此描述对当前算法不适用-2017/09/21)\n\n\"1.9.22.0-255\"\n\"1.9.0.0/16\"\n\"1.9.22.0-255\"\n\"1.9.22.0/24\"\n\"1.9.22.0-255\"\n\"1.9.22.0-1.9.22.100\"\n\"1.9.22.0-1.9.22.255\"\n\"1.9.0.0/16\"\n\"3.3.3.0/24\"\n\"3.3.0.0/16\"\n\"3.3.3.0-255\"\n\"1.1.1.0/24\"\n\"1.9.0.0/16\"\n\"2001:db8::1/128\"\n\n\t|\n\t|\n\tv\n\n[1.9.0.0/16 3.3.0.0/16 1.1.1.0/24 203.0.113.0/24 2001:db8::1/128]\n*/\nfunc dedup(s []ipaddr.Prefix) []ipaddr.Prefix {\n\tsort.Slice(s, func(i int, j int) bool {\n\t\treturn s[i].String() < s[j].String()\n\t})\n\tout := s[:1]\n\tt := s[0]\n\tfor _, s := range s[1:] {\n\t\tif !t.Contains(&s) && !t.Equal(&s) {\n\t\t\tout = append(out, s)\n\t\t\tt = s\n\t\t}\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "iprange_quic.txt.example",
    "content": "108.177.97.0/24\r\n113.171.202.0/24\r\n113.171.216.0/24\r\n113.171.220.0/24\r\n113.171.232.0/24\r\n113.171.236.0/24\r\n113.171.237.0/24\r\n113.171.238.0/24\r\n121.158.53.0/24\r\n121.78.42.0/24\r\n122.202.129.0/24\r\n123.241.255.0/24\r\n124.216.0.0/24\r\n157.197.92.0/24\r\n157.197.93.0/24\r\n182.163.240.0/24\r\n183.91.238.0/24\r\n203.233.10.0/24\r\n203.233.37.0/24\r\n203.233.88.0/24\r\n203.233.92.0/24\r\n203.233.96.0/24\r\n203.248.132.0/24\r\n203.252.15.0/24\r\n211.49.146.0/24\r\n221.144.116.0/24\r\n223.62.225.0/24\r\n49.231.113.0/24\r\n61.62.98.0/24\r\n61.90.179.0/24\r\n61.90.189.0/24\r\n61.91.8.0/24\r\n61.91.9.0/24\r\n222.251.134.0/24"
  },
  {
    "path": "ping.go",
    "content": "package main\n\n// Copyright 2009 The Go Authors.  All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// taken from http://golang.org/src/pkg/net/ipraw_test.go\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n)\n\nfunc testPing(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool {\n\tstart := time.Now()\n\tif err := Pinger(ip, config.ScanMaxRTT); err != nil {\n\t\treturn false\n\t}\n\tif rtt := time.Since(start); rtt > config.ScanMinRTT {\n\t\trecord.RTT += rtt\n\t\treturn true\n\t}\n\treturn false\n}\n\nconst (\n\ticmpv4EchoRequest = 8\n\ticmpv4EchoReply   = 0\n\ticmpv6EchoRequest = 128\n\ticmpv6EchoReply   = 129\n)\n\nvar ErrPingConnFailed = errors.New(\"ping: connect failed\")\n\ntype icmpMessage struct {\n\tType     int             // type\n\tCode     int             // code\n\tChecksum int             // checksum\n\tBody     icmpMessageBody // body\n}\n\ntype icmpMessageBody interface {\n\tLen() int\n\tMarshal() ([]byte, error)\n}\n\n// Marshal returns the binary enconding of the ICMP echo request or\n// reply message m.\nfunc (m *icmpMessage) Marshal() ([]byte, error) {\n\tb := []byte{byte(m.Type), byte(m.Code), 0, 0}\n\tif m.Body != nil && m.Body.Len() != 0 {\n\t\tmb, err := m.Body.Marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb = append(b, mb...)\n\t}\n\tswitch m.Type {\n\tcase icmpv6EchoRequest, icmpv6EchoReply:\n\t\treturn b, nil\n\t}\n\tcsumcv := len(b) - 1 // checksum coverage\n\ts := uint32(0)\n\tfor i := 0; i < csumcv; i += 2 {\n\t\ts += uint32(b[i+1])<<8 | uint32(b[i])\n\t}\n\tif csumcv&1 == 0 {\n\t\ts += uint32(b[csumcv])\n\t}\n\ts = s>>16 + s&0xffff\n\ts = s + s>>16\n\t// Place checksum back in header; using ^= avoids the\n\t// assumption the checksum bytes are zero.\n\tb[2] ^= byte(^s & 0xff)\n\tb[3] ^= byte(^s >> 8)\n\treturn b, nil\n}\n\n// parseICMPMessage parses b as an ICMP message.\nfunc parseICMPMessage(b []byte) (*icmpMessage, error) {\n\tmsglen := len(b)\n\tif msglen < 4 {\n\t\treturn nil, errors.New(\"message too short\")\n\t}\n\tm := &icmpMessage{Type: int(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])}\n\tif msglen > 4 {\n\t\tvar err error\n\t\tswitch m.Type {\n\t\tcase icmpv4EchoRequest, icmpv4EchoReply, icmpv6EchoRequest, icmpv6EchoReply:\n\t\t\tm.Body, err = parseICMPEcho(b[4:])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\treturn m, nil\n}\n\n// imcpEcho represenets an ICMP echo request or reply message body.\ntype icmpEcho struct {\n\tID   int    // identifier\n\tSeq  int    // sequence number\n\tData []byte // data\n}\n\nfunc (p *icmpEcho) Len() int {\n\tif p == nil {\n\t\treturn 0\n\t}\n\treturn 4 + len(p.Data)\n}\n\n// Marshal returns the binary enconding of the ICMP echo request or\n// reply message body p.\nfunc (p *icmpEcho) Marshal() ([]byte, error) {\n\tb := make([]byte, 4+len(p.Data))\n\tb[0], b[1] = byte(p.ID>>8), byte(p.ID&0xff)\n\tb[2], b[3] = byte(p.Seq>>8), byte(p.Seq&0xff)\n\tcopy(b[4:], p.Data)\n\treturn b, nil\n}\n\n// parseICMPEcho parses b as an ICMP echo request or reply message body.\nfunc parseICMPEcho(b []byte) (*icmpEcho, error) {\n\tbodylen := len(b)\n\tp := &icmpEcho{ID: int(b[0])<<8 | int(b[1]), Seq: int(b[2])<<8 | int(b[3])}\n\tif bodylen > 4 {\n\t\tp.Data = make([]byte, bodylen-4)\n\t\tcopy(p.Data, b[4:])\n\t}\n\treturn p, nil\n}\n\nfunc Ping(address string, timeout time.Duration) error {\n\treturn Pinger(address, timeout)\n}\n\nfunc Pinger(address string, timeout time.Duration) error {\n\ttyp := icmpv4EchoRequest\n\tnetwork := \"ip4:icmp\"\n\n\tisIpv6 := false\n\tif ip := net.ParseIP(address); ip != nil && ip.To4() == nil {\n\t\ttyp = icmpv6EchoRequest\n\t\tnetwork = \"ip6:ipv6-icmp\"\n\t\tisIpv6 = true\n\t}\n\n\tc, err := net.Dial(network, address)\n\tif err != nil {\n\t\treturn ErrPingConnFailed\n\t}\n\tdefer c.Close()\n\tdeadline := time.Now().Add(timeout)\n\tc.SetReadDeadline(deadline)\n\n\txid, xseq := os.Getpid()&0xffff, 1\n\twb, err := (&icmpMessage{\n\t\tType: typ, Code: 0,\n\t\tBody: &icmpEcho{\n\t\t\tID: xid, Seq: xseq,\n\t\t\tData: bytes.Repeat([]byte(\"Go Go Gadget Ping!!!\"), 3),\n\t\t},\n\t}).Marshal()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, err = c.Write(wb); err != nil {\n\t\treturn err\n\t}\n\tvar m *icmpMessage\n\trb := make([]byte, 20+len(wb))\n\tfor {\n\t\t// read_timeout := deadline.Sub(time.Now())\n\t\t// c.SetReadDeadline(time.Now().Add(read_timeout))\n\t\tif _, err = c.Read(rb); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !isIpv6 {\n\t\t\trb = ipv4Payload(rb)\n\t\t}\n\t\tif m, err = parseICMPMessage(rb); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tswitch m.Type {\n\t\tcase icmpv4EchoRequest, icmpv6EchoRequest:\n\t\t\tcontinue\n\t\t}\n\t\tbreak\n\t}\n\treturn nil\n}\n\nfunc ipv4Payload(b []byte) []byte {\n\tif len(b) < 20 {\n\t\treturn b\n\t}\n\thdrlen := int(b[0]&0x0f) << 2\n\treturn b[hdrlen:]\n}\n"
  },
  {
    "path": "quic.go",
    "content": "package main\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"crypto/tls\"\r\n\t\"io\"\r\n\t\"math/rand\"\r\n\t\"net\"\r\n\t\"net/http\"\r\n\t\"strings\"\r\n\t\"time\"\r\n\r\n\tquic \"github.com/quic-go/quic-go\"\r\n\t\"github.com/quic-go/quic-go/http3\"\r\n)\r\n\r\nvar errNoSuchBucket = []byte(\"<?xml version='1.0' encoding='UTF-8'?><Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist.</Message></Error>\")\r\n\r\nfunc testQuic(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool {\r\n\tstart := time.Now()\r\n\r\n\tquicCfg := &quic.Config{\r\n\t\tHandshakeIdleTimeout: config.HandshakeTimeout,\r\n\t\tKeepAlivePeriod:      0,\r\n\t}\r\n\r\n\tserverName := \"\"\r\n\tif len(config.ServerName) == 0 {\r\n\t\tserverName = randomHost()\r\n\t} else {\r\n\t\tserverName = randomChoice(config.ServerName)\r\n\t}\r\n\r\n\ttlsCfg := &tls.Config{\r\n\t\tInsecureSkipVerify: true,\r\n\t\tServerName:         serverName,\r\n\t\tNextProtos:         []string{\"h3-29\", \"h3\", \"hq\", \"quic\"},\r\n\t}\r\n\r\n\tctx, cancel := context.WithTimeout(ctx, config.ScanMaxRTT)\r\n\tdefer cancel()\r\n\r\n\tquicConn, err := quic.DialAddrEarly(ctx, net.JoinHostPort(ip, \"443\"), tlsCfg, quicCfg)\r\n\tif err != nil {\r\n\t\treturn false\r\n\t}\r\n\tdefer quicConn.CloseWithError(0, \"\")\r\n\r\n\t// lv1 只会验证证书是否存在\r\n\tcs := quicConn.ConnectionState().TLS\r\n\tif !cs.HandshakeComplete || len(cs.PeerCertificates) < 2 {\r\n\t\treturn false\r\n\t}\r\n\r\n\t// lv2 验证证书是否正确\r\n\tif config.Level > 1 {\r\n\t\tpkp := cs.PeerCertificates[1].RawSubjectPublicKeyInfo\r\n\t\tif !bytes.Equal(gpkp, pkp) {\r\n\t\t\treturn false\r\n\t\t}\r\n\t}\r\n\r\n\t// lv3 使用 http 访问来验证\r\n\tif config.Level > 2 {\r\n\t\ttr := &http3.RoundTripper{DisableCompression: true}\r\n\t\tdefer tr.Close()\r\n\t\ttr.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {\r\n\t\t\treturn quicConn, err\r\n\t\t}\r\n\t\t// 设置超时\r\n\t\thclient := &http.Client{\r\n\t\t\tTransport: tr,\r\n\t\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\r\n\t\t\t\treturn http.ErrUseLastResponse\r\n\t\t\t},\r\n\t\t\tTimeout: config.ScanMaxRTT - time.Since(start),\r\n\t\t}\r\n\t\turl := \"https://\" + config.HTTPVerifyHosts[rand.Intn(len(config.HTTPVerifyHosts))]\r\n\t\treq, _ := http.NewRequest(http.MethodGet, url, nil)\r\n\t\treq.Close = true\r\n\t\tresp, _ := hclient.Do(req)\r\n\t\tif resp == nil || (resp.StatusCode < 200 || resp.StatusCode >= 400) || !strings.Contains(resp.Header.Get(\"Alt-Svc\"), `quic=\":443\"`) {\r\n\t\t\treturn false\r\n\t\t}\r\n\t\tif resp.Body != nil {\r\n\t\t\tdefer resp.Body.Close()\r\n\t\t\t// lv4 验证是否是 NoSuchBucket 错误\r\n\t\t\tif config.Level > 3 && resp.Header.Get(\"Content-Type\") == \"application/xml; charset=UTF-8\" { // 也许条件改为 || 更好\r\n\t\t\t\tbody, err := io.ReadAll(resp.Body)\r\n\t\t\t\tif err != nil || bytes.Equal(body, errNoSuchBucket) {\r\n\t\t\t\t\treturn false\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tio.Copy(io.Discard, resp.Body)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif rtt := time.Since(start); rtt > config.ScanMinRTT {\r\n\t\trecord.RTT += rtt\r\n\t\treturn true\r\n\t}\r\n\treturn false\r\n}\r\n"
  },
  {
    "path": "scan.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\ntype ScanRecord struct {\n\tIP  string\n\tRTT time.Duration\n}\n\ntype ScanRecords struct {\n\trecordMutex sync.Mutex\n\trecords     []*ScanRecord\n\tscanCounter int32\n}\n\nfunc (srs *ScanRecords) AddRecord(rec *ScanRecord) {\n\tsrs.recordMutex.Lock()\n\tsrs.records = append(srs.records, rec)\n\tsrs.recordMutex.Unlock()\n\tlog.Printf(\"Found a record: IP=%s, RTT=%s\\n\", rec.IP, rec.RTT.String())\n}\n\nfunc (srs *ScanRecords) IncScanCounter() {\n\tscanCount := atomic.AddInt32(&srs.scanCounter, 1)\n\tif scanCount%1000 == 0 {\n\t\tlog.Printf(\"Scanned %d IPs, Found %d records\\n\", scanCount, srs.RecordSize())\n\t}\n}\n\nfunc (srs *ScanRecords) RecordSize() int {\n\tsrs.recordMutex.Lock()\n\tdefer srs.recordMutex.Unlock()\n\treturn len(srs.records)\n}\n\nfunc (srs *ScanRecords) ScanCount() int32 {\n\treturn atomic.LoadInt32(&srs.scanCounter)\n}\n\nfunc (srs *ScanRecords) Records() []*ScanRecord {\n\tsrs.recordMutex.Lock()\n\tdefer srs.recordMutex.Unlock()\n\treturn srs.records\n}\n\ntype testIPFunc func(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool\n\nfunc testip(ctx context.Context, testFunc testIPFunc, ip string, config *ScanConfig) *ScanRecord {\n\trecord := new(ScanRecord)\n\tfor i := 0; i < config.ScanCountPerIP; i++ {\n\t\tif !testFunc(ctx, ip, config, record) {\n\t\t\treturn nil\n\t\t}\n\t}\n\trecord.IP = ip\n\trecord.RTT = record.RTT / time.Duration(config.ScanCountPerIP)\n\treturn record\n}\n\nfunc (gs *GScanner) testIPWorker(ctx context.Context, ipQueue chan string) {\n\tcfg, testFunc := gs.getScanConfig(gs.ScanMode)\n\n\tfor ip := range ipQueue {\n\t\t// log.Printf(\"Start testing IP: %s\", ip)\n\n\t\tif gs.VerifyPing {\n\t\t\tstart := time.Now()\n\n\t\t\tpingErr := Ping(ip, gs.ScanMaxPingRTT)\n\t\t\tif pingErr != nil || time.Since(start) < gs.ScanMinPingRTT {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\tr := testip(ctx, testFunc, ip, cfg)\n\t\t\tif r != nil {\n\t\t\t\tgs.AddRecord(r) // 这里放到前面，扫描时可能会多出一些记录, 但是不影响\n\t\t\t\tif gs.RecordSize() >= cfg.RecordLimit {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tgs.IncScanCounter() // 扫描完后才增加计数\n\t\t}\n\n\t}\n}\n\nfunc (gs *GScanner) StartScan(ipQueue chan string) {\n\tctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)\n\tdefer stop()\n\n\tn := gs.ScanWorker\n\tops(n, n, func(i, thread int) {\n\t\tgs.testIPWorker(ctx, ipQueue)\n\t})\n}\n"
  },
  {
    "path": "sni.go",
    "content": "package main\r\n\r\nimport (\r\n\t\"context\"\r\n\t\"crypto/tls\"\r\n\t\"net\"\r\n\t\"net/http\"\r\n\t\"net/http/httputil\"\r\n\t\"time\"\r\n)\r\n\r\nfunc testSni(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool {\r\n\ttlscfg := &tls.Config{\r\n\t\tInsecureSkipVerify: true,\r\n\t}\r\n\r\n\tfor _, serverName := range config.ServerName {\r\n\t\tstart := time.Now()\r\n\r\n\t\tctx, cancel := context.WithTimeout(ctx, config.ScanMaxRTT)\r\n\t\tdefer cancel()\r\n\r\n\t\tconn, err := (&net.Dialer{}).DialContext(ctx, \"tcp\", net.JoinHostPort(ip, \"443\"))\r\n\t\tif err != nil {\r\n\t\t\treturn false\r\n\t\t}\r\n\r\n\t\ttlscfg.ServerName = serverName\r\n\t\ttlsconn := tls.Client(conn, tlscfg)\r\n\t\ttlsconn.SetDeadline(time.Now().Add(config.HandshakeTimeout))\r\n\t\tif err = tlsconn.Handshake(); err != nil {\r\n\t\t\ttlsconn.Close()\r\n\t\t\treturn false\r\n\t\t}\r\n\t\tif config.Level > 1 {\r\n\t\t\tpcs := tlsconn.ConnectionState().PeerCertificates\r\n\t\t\tif len(pcs) == 0 || pcs[0].Subject.CommonName != serverName {\r\n\t\t\t\ttlsconn.Close()\r\n\t\t\t\treturn false\r\n\t\t\t}\r\n\t\t}\r\n\t\tif config.Level > 2 {\r\n\t\t\treq, err := http.NewRequest(http.MethodHead, \"https://\"+serverName, nil)\r\n\t\t\tif err != nil {\r\n\t\t\t\ttlsconn.Close()\r\n\t\t\t\treturn false\r\n\t\t\t}\r\n\t\t\ttlsconn.SetDeadline(time.Now().Add(config.ScanMaxRTT - time.Since(start)))\r\n\t\t\tresp, err := httputil.NewClientConn(tlsconn, nil).Do(req)\r\n\t\t\tif err != nil {\r\n\t\t\t\ttlsconn.Close()\r\n\t\t\t\treturn false\r\n\t\t\t}\r\n\t\t\t// io.Copy(os.Stdout, resp.Body)\r\n\t\t\t// if resp.Body != nil {\r\n\t\t\t// \tio.Copy(io.Discard, resp.Body)\r\n\t\t\t// \tresp.Body.Close()\r\n\t\t\t// }\r\n\t\t\tif resp.StatusCode >= 400 {\r\n\t\t\t\ttlsconn.Close()\r\n\t\t\t\treturn false\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\ttlsconn.Close()\r\n\r\n\t\trtt := time.Since(start)\r\n\t\tif rtt < config.ScanMinRTT {\r\n\t\t\treturn false\r\n\t\t}\r\n\t\trecord.RTT += rtt\r\n\t}\r\n\treturn true\r\n}\r\n"
  },
  {
    "path": "tls.go",
    "content": "package main\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"crypto/tls\"\r\n\t\"encoding/base64\"\r\n\t\"io\"\r\n\t\"math/rand\"\r\n\t\"net\"\r\n\t\"net/http\"\r\n\t\"time\"\r\n)\r\n\r\nvar gpkp, _ = base64.StdEncoding.DecodeString(\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9Yjf52KMHjf4N0KQf2yH0PtlgiX96MtrpP9t6Voj4pn2HOmSA5kTfAkKivpC1l5WJKp6M4Qf0elpu7l07FdMZmiTdzdVU/45EE23NLtfJXc3OxeU6jzlndW8w7RD6y6nR++wRBFj2LRBhd1BMEiTG7+39uBFAiHglkIXz9krZVY0ByYEDaj9fcou7+pIfDdNPwCfg9/vdYQueVdc/FduGpb//Iyappm+Jdl/liwG9xEqAoCA62MYPFBJh+WKyl8ZK1mWgQCg+1HbyncLC8mWT+9wScdcbSD9mbS04soud/0t3Au2axMMjBkrF5aYufCL9qAnu7bjjVGPva7Hm7GJnQIDAQAB\")\r\n\r\nfunc testTls(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool {\r\n\tstart := time.Now()\r\n\r\n\tctx, cancel := context.WithTimeout(ctx, config.ScanMaxRTT)\r\n\tdefer cancel()\r\n\r\n\tvar dialer net.Dialer\r\n\tconn, err := dialer.DialContext(ctx, \"tcp\", net.JoinHostPort(ip, \"443\"))\r\n\tif err != nil {\r\n\t\treturn false\r\n\t}\r\n\tdefer conn.Close()\r\n\r\n\tvar serverName string\r\n\tif len(config.ServerName) == 0 {\r\n\t\tserverName = randomHost()\r\n\t} else {\r\n\t\tserverName = randomChoice(config.ServerName)\r\n\t}\r\n\r\n\ttlscfg := &tls.Config{\r\n\t\tInsecureSkipVerify: true,\r\n\t\tMinVersion:         tls.VersionTLS11,\r\n\t\tMaxVersion:         tls.VersionTLS12,\r\n\t\tCipherSuites: []uint16{\r\n\t\t\ttls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,\r\n\t\t\ttls.TLS_RSA_WITH_AES_128_CBC_SHA256,\r\n\t\t\ttls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,\r\n\t\t},\r\n\t\tServerName: serverName,\r\n\t}\r\n\r\n\ttlsconn := tls.Client(conn, tlscfg)\r\n\tdefer tlsconn.Close()\r\n\r\n\ttlsconn.SetDeadline(time.Now().Add(config.HandshakeTimeout))\r\n\tif err = tlsconn.Handshake(); err != nil {\r\n\t\treturn false\r\n\t}\r\n\tif config.Level > 1 {\r\n\t\tpcs := tlsconn.ConnectionState().PeerCertificates\r\n\t\tif pcs == nil || len(pcs) < 2 {\r\n\t\t\treturn false\r\n\t\t}\r\n\t\tif org := pcs[1].Subject.Organization; len(org) == 0 || org[0] != \"Google Trust Services LLC\" {\r\n\t\t\treturn false\r\n\t\t}\r\n\t\tpkp := pcs[1].RawSubjectPublicKeyInfo\r\n\t\tif !bytes.Equal(gpkp, pkp) {\r\n\t\t\treturn false\r\n\t\t}\r\n\t}\r\n\tif config.Level > 2 {\r\n\t\turl := \"https://\" + config.HTTPVerifyHosts[rand.Intn(len(config.HTTPVerifyHosts))]\r\n\t\treq, _ := http.NewRequest(http.MethodGet, url, nil)\r\n\t\treq.Close = true\r\n\t\tc := http.Client{\r\n\t\t\tTransport: &http.Transport{\r\n\t\t\t\tDialTLS: func(network, addr string) (net.Conn, error) { return tlsconn, nil },\r\n\t\t\t},\r\n\t\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\r\n\t\t\t\treturn http.ErrUseLastResponse\r\n\t\t\t},\r\n\t\t\tTimeout: config.ScanMaxRTT - time.Since(start),\r\n\t\t}\r\n\t\tresp, _ := c.Do(req)\r\n\t\tif resp == nil || (resp.StatusCode < 200 || resp.StatusCode >= 400) {\r\n\t\t\treturn false\r\n\t\t}\r\n\t\tif resp.Body != nil {\r\n\t\t\tio.Copy(io.Discard, resp.Body)\r\n\t\t\tresp.Body.Close()\r\n\t\t}\r\n\t}\r\n\r\n\tif rtt := time.Since(start); rtt > config.ScanMinRTT {\r\n\t\trecord.RTT += rtt\r\n\t\treturn true\r\n\t}\r\n\treturn false\r\n}\r\n"
  },
  {
    "path": "util.go",
    "content": "package main\r\n\r\nimport (\r\n\t\"bufio\"\r\n\t\"bytes\"\r\n\t\"encoding/json\"\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"math/rand\"\r\n\t\"os\"\r\n\t\"path\"\r\n\t\"strings\"\r\n\t\"sync\"\r\n)\r\n\r\n// 代码来自: goproxy\r\nfunc readJsonConfig(filename string, config interface{}) error {\r\n\tfileext := path.Ext(filename)\r\n\tfilename1 := strings.TrimSuffix(filename, fileext) + \".user\" + fileext\r\n\r\n\tcm := make(map[string]interface{})\r\n\tfor i, name := range []string{filename, filename1} {\r\n\t\tdata, err := os.ReadFile(name)\r\n\t\tif err != nil {\r\n\t\t\tif i == 0 {\r\n\t\t\t\treturn err\r\n\t\t\t} else {\r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\t\t}\r\n\t\tdata = bytes.TrimPrefix(data, []byte(\"\\xef\\xbb\\xbf\"))\r\n\t\tdata, err = readJson(bytes.NewReader(data))\r\n\t\tif err != nil {\r\n\t\t\treturn err\r\n\t\t}\r\n\r\n\t\tcm1 := make(map[string]interface{})\r\n\r\n\t\td := json.NewDecoder(bytes.NewReader(data))\r\n\t\td.UseNumber()\r\n\r\n\t\tif err = d.Decode(&cm1); err != nil {\r\n\t\t\treturn err\r\n\t\t}\r\n\r\n\t\tif err = mergeMap(cm, cm1); err != nil {\r\n\t\t\treturn err\r\n\t\t}\r\n\t}\r\n\r\n\tdata, err := json.Marshal(cm)\r\n\tif err != nil {\r\n\t\treturn err\r\n\t}\r\n\r\n\td := json.NewDecoder(bytes.NewReader(data))\r\n\td.UseNumber()\r\n\r\n\treturn d.Decode(config)\r\n}\r\n\r\nfunc readJson(r io.Reader) ([]byte, error) {\r\n\tscanner := bufio.NewScanner(r)\r\n\tvar b bytes.Buffer\r\n\tprev := \"\"\r\n\tfor scanner.Scan() {\r\n\t\tline := strings.TrimSpace(scanner.Text())\r\n\t\tif line == \"\" || strings.HasPrefix(line, \"//\") {\r\n\t\t\tcontinue\r\n\t\t}\r\n\t\tif strings.HasPrefix(line, \"}\") || strings.HasPrefix(line, \"]\") {\r\n\t\t\tprev = strings.TrimSuffix(prev, \",\")\r\n\t\t}\r\n\r\n\t\tb.WriteString(prev)\r\n\t\tprev = line\r\n\t}\r\n\tb.WriteString(prev)\r\n\treturn b.Bytes(), scanner.Err()\r\n}\r\n\r\nfunc mergeMap(m1 map[string]interface{}, m2 map[string]interface{}) error {\r\n\tfor key, value := range m2 {\r\n\r\n\t\tm1v, m1_has_key := m1[key]\r\n\t\tm2v, m2v_is_map := value.(map[string]interface{})\r\n\t\tm1v1, m1v_is_map := m1v.(map[string]interface{})\r\n\r\n\t\tswitch {\r\n\t\tcase !m1_has_key, !m2v_is_map:\r\n\t\t\tm1[key] = value\r\n\t\tcase !m1v_is_map:\r\n\t\t\treturn fmt.Errorf(\"m1v=%#v is not a map, but m2v=%#v is a map\", m1v, m2v)\r\n\t\tdefault:\r\n\t\t\tmergeMap(m1v1, m2v)\r\n\t\t}\r\n\t}\r\n\r\n\treturn nil\r\n}\r\n\r\nfunc randInt(l, u int) int {\r\n\treturn rand.Intn(u-l) + l\r\n}\r\n\r\nfunc randomChoice[T any](a []T) T {\r\n\treturn a[rand.Intn(len(a))]\r\n}\r\n\r\n// 生成两段或三段的随机字符串当作 host\r\n// llm.xadl\r\n// unupk.bfrf.pvi\r\nfunc randomHost() string {\r\n\ta := make([][]byte, randInt(2, 4))\r\n\tfor i := range a {\r\n\t\tm := randInt(3, 7)\r\n\t\tb := make([]byte, m)\r\n\t\tfor j := 0; j < m; j++ {\r\n\t\t\tb[j] = byte(randInt(97, 122))\r\n\t\t}\r\n\t\ta[i] = b\r\n\t}\r\n\treturn string(bytes.Join(a, []byte{46}))\r\n}\r\n\r\nfunc or[T comparable](vals ...T) T {\r\n\tvar zero T\r\n\tfor _, val := range vals {\r\n\t\tif val != zero {\r\n\t\t\treturn val\r\n\t\t}\r\n\t}\r\n\treturn zero\r\n}\r\n\r\n// pathExist 返回文件或文件夹是否存在\r\nfunc pathExist(name string) bool {\r\n\t_, err := os.Stat(name)\r\n\treturn !os.IsNotExist(err)\r\n}\r\n\r\nfunc ops(count, threads int, op func(i, thread int)) {\r\n\tvar wg sync.WaitGroup\r\n\twg.Add(threads)\r\n\tfor i := 0; i < threads; i++ {\r\n\t\ts, e := count/threads*i, count/threads*(i+1)\r\n\t\tif i == threads-1 {\r\n\t\t\te = count\r\n\t\t}\r\n\t\tgo func(i, s, e int) {\r\n\t\t\tfor j := s; j < e; j++ {\r\n\t\t\t\top(j, i)\r\n\t\t\t}\r\n\t\t\twg.Done()\r\n\t\t}(i, s, e)\r\n\t}\r\n\twg.Wait()\r\n}\r\n"
  }
]