Full Code of Kisesy/gscan_quic for AI

master 71e5f5ae0150 cached
20 files
45.3 KB
18.2k tokens
48 symbols
1 requests
Download .txt
Repository: Kisesy/gscan_quic
Branch: master
Commit: 71e5f5ae0150
Files: 20
Total size: 45.3 KB

Directory structure:
gitextract_kl6s_cna/

├── .github/
│   └── workflows/
│       ├── go.yml
│       └── release.yml
├── .gitignore
├── .goreleaser.yaml
├── .travis.yml
├── LICENSE
├── README.md
├── config.json
├── config.user.json.example
├── go.mod
├── go.sum
├── gscan.go
├── iprange.go
├── iprange_quic.txt.example
├── ping.go
├── quic.go
├── scan.go
├── sni.go
├── tls.go
└── util.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/go.yml
================================================
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go

name: Go

on:
  push:
    branches: ["master"]
  pull_request:
    branches: ["master"]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: "1.20"

      - name: Install dependencies
        run: go get .

      - name: Build
        run: go build -v ./...

      - name: Test
        run: go test -v ./...


================================================
FILE: .github/workflows/release.yml
================================================
name: goreleaser

on:
  push:
    # run only against tags
    tags:
      - "*"

permissions:
  contents: write
  # packages: write
  # issues: write

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - run: git fetch --force --tags
      - uses: actions/setup-go@v4
        with:
          go-version: "1.20"
      # More assembly might be required: Docker logins, GPG, etc. It all depends
      # on your needs.
      - uses: goreleaser/goreleaser-action@v4
        with:
          # either 'goreleaser' (default) or 'goreleaser-pro':
          distribution: goreleaser
          version: latest
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          # Your GoReleaser Pro key, if you are using the 'goreleaser-pro'
          # distribution:
          # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}


================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
*.zip
*.7z

out*.txt

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
gscan_quic/
backup/
bin/


================================================
FILE: .goreleaser.yaml
================================================
archives:
  - id: "gscan_quic"
    format_overrides:
      - goos: windows
        format: zip
    files:
      - LICENSE
      - README.md
      - config.json
      - "*.example"


================================================
FILE: .travis.yml
================================================
language: go

go:
- "1.10"

before_install:
- go get github.com/Kisesy/gscan_quic
- sudo apt-get install p7zip-full

script:
- export CGO_ENABLED=0
- export EXE=gscan_quic
- export TAR_EXCLUDE="--exclude=*.go --exclude=*.bz2 --exclude=*.7z --exclude=*.xz --exclude=vendor"

- echo ${TRAVIS_TAG} > Version.txt

- export GOOS=windows GOARCH=386 && go build -ldflags "-w -s"
- 7za a ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.7z -ms=on -mx=9 -r- -mmt -x'!*.go' -x'!*.7z' -x'!*.zip' -x'!vendor/' *
- rm *.exe
- export GOOS=windows GOARCH=amd64 && go build -ldflags "-w -s"
- 7za a ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.7z -ms=on -mx=9 -r- -mmt -x'!*.go' -x'!*.7z' -x'!*.zip' -x'!vendor/' *
- rm *.exe

- export GOOS=linux GOARCH=386 XZ_OPT=-9 && go build -ldflags "-w -s"
- tar cvJpf ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.tar.xz * ${TAR_EXCLUDE}
- export GOOS=linux GOARCH=amd64 XZ_OPT=-9 && go build -ldflags "-w -s"
- tar cvJpf ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.tar.xz * ${TAR_EXCLUDE}

- export GOOS=linux GOARCH=arm && go build -ldflags "-w -s"
- tar cjf ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.tar.bz2 * ${TAR_EXCLUDE}
- export GOOS=darwin GOARCH=amd64 && go build -ldflags "-w -s"
- tar cjf ${EXE}_${GOOS}_${GOARCH}-${TRAVIS_TAG}.tar.bz2 * ${TAR_EXCLUDE}

- md5sum *.7z *.xz *.bz2 | tee md5sum
- sha1sum *.7z *.xz *.bz2 | tee sha1sum

deploy:
  provider: releases
  api_key:
    secure: ${TOKEN}
  file_glob: true
  file: 
    - "*.7z"
    - "*.xz"
    - "*.bz2"
    - "md5sum"
    - "sha1sum"
  skip_cleanup: true
  on:
    tags: true

notifications:
  email: false

================================================
FILE: LICENSE
================================================
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org>


================================================
FILE: README.md
================================================

# gscan-quic

一个 IP 可用性扫描工具

## 当前支持

- [x] QUIC
- [x] SNI
- [x] TLS
- [x] PING
- [ ] SOCKS4/SOCKS4A/SOCKS5

## 简单说明

因为支持多种类型的IP扫描, 所以 IP 段文件是分开放的.

比如 quic 的IP段文件是 iprange_quic.txt, tls 的就是 iprange_tls.txt

其他类型的都可以在 config.json 文件里看到

**IP段文件格式如下:**

    # 下面几种都是支持的, 遇到错误IP格式是会跳过的

    1.9.23.0            
    1.9.23.0/24
    1.9.0.0/16
    
    1.9.22.0-1.9.23.0/24
    
    1.9.22.0/24-1.9.33.0/24
    
    1.9.22.0-255
    1.9.22.0-1.9.22.0
    1.9.22.0-1.9.22.255
    1.9.22.0-1.9.33.255

    1.9.22.111-1.9.22.111    会自动精简成 1.9.22.111
    1.9.22.0/24-1.9.22.0/24  会自动精简成 1.9.22.0/24

    # 支持 ipv6 格式

    2001:db8::1
    2001:db8::1/128

    # 支持 gop 的 "xxx","xxx" 和 goa 的 xxx|xxx 格式

    "1.9.22.0", "1.9.22.1","1.9.22.2",
    1.9.22.0|1.9.22.1|

    # IP段也是会自动去重的

    1.9.22.0-255
    1.9.0.0/16
    1.9.22.0-255
    1.9.22.0/24
    1.9.22.0-255
    1.9.22.0-1.9.22.100
    1.9.22.0-1.9.22.255
    1.9.0.0/16
    3.3.3.0/24
    3.3.0.0/16
    3.3.3.0-255
    1.1.1.0/24
    1.9.0.0/16
    # 上面几个经过去重只会留下
    3.3.0.0/16
    1.9.0.0/16
    1.1.1.0/24

> **注意:**

* 默认是有输出个数限制的, 可以设置配置文件里的 RecordLimit

* 在扫描过程中是可以中断的, 只要按 <kbd>Ctrl</kbd>+<kbd>C</kbd> 就可以中断, 扫过的IP是会保留的

* 扫描IP段是随机的

* 如果IP段是 xx|xx 或 "xxx","xxx" 格式的, 那么一行的字节加起来大小不能超过4MB, 如有超过, 必须分行, 否则会跳过这一行


## 配置说明

参考 config.json 文件

每次更新时最好覆盖掉 config.json 文件

支持 gop 形式的个人配置 config.user.json, 所以为了防止每次修改, 可以自己新建一个.

## 下载
到 https://github.com/Kisesy/gscan_quic/releases 下载编译好的

## 感谢

改自 yinqiwen 大神的 https://github.com/yinqiwen/gscan 在此感谢


================================================
FILE: config.json
================================================
{
	// 注意:
	// [扫描并发数] 理论上设置越大扫的越快, 但是并不意味越大就可以扫到更多IP
	// 大量的并发, 会造成网络堵塞, 甚至触到系统网络的限制或造成路由器宕机而出现更多的问题
	// 所以如果在你那里基本扫不到IP, 可以试着减小扫描并发数并增大超时时间

	// 新添了 Level 设置
	// 可以按照说明进行修改来达到加速扫描的效果, 但是为了结果的准确性最好还是设置为最高

	// 扫描并发数
	"ScanWorker": 100,

	// 是否启用 Ping, 每次扫描 Tls、Quic...前都会先 ping 一下
	"VerifyPing": false,
	// Ping 的参数
	"ScanMinPingRTT": 80,
	"ScanMaxPingRTT": 800,

	// 是否开启备份
	// 每次扫到的IP,都会在此目录下备份一份
	"EnableBackup": true,
	"BackupDir": "./backup",
	
	// 是否禁用结束扫描时的命令行暂停
	"DisablePause": false,

	// 扫描方式, 可以设置为下面的任意一个, 大小写都可以
	"ScanMode": "quic",

	// 如果设置为 ping, VerifyPing 会自动关闭
	"Ping": {
		"ScanCountPerIP": 1,
		"ScanMinRTT": 0,
		"ScanMaxRTT": 1000,
		"RecordLimit": 10000,
		"OutputSeparator": "\r\n",
		// 支持带端口格式的IP, 只是为了使用方便, 并不是真的支持端口, 端口会被自动去除
		"InputFile": "./iprange_ping.txt",
		"OutputFile": "./out_ping.txt",
	},

	"QUIC": {
		// 每个IP的测试次数
		"ScanCountPerIP": 1,
		// ServerName 可以设置多项, 会随机选择一个
		// 默认空列表,随机生成域名格式字符串
		// 如果要设置为空, 可以写为 [""]
		"ServerName": [],
		// HTTPVerifyHosts 是 http 测试时使用的测试地址
		// 如果设置多个, 将会随机选择一个进行测试
		"HTTPVerifyHosts": ["dns.google.com"],
		// 握手超时时间, 单位: 毫秒
		"HandshakeTimeout": 2500,
		"ScanMinRTT": 0,
		"ScanMaxRTT": 3000,
		// 结果的限制数量, 比如: 设置成 100, 将会扫到100个IP后自动结束
		"RecordLimit": 10000,
		// 输出结果的分隔符, 比如: 如果想要换行输出, 可以改为: \n
		// 有个特殊的例外, 如果设置为 gop, 则会输出 "xxx", "xxx" 样式
		"OutputSeparator": "gop",
		// IP 或 IP 段文件
		"InputFile": "./iprange_quic.txt",
		// 输出的文件路径
		"OutputFile": "./out_quic.txt",
		// 验证等级
		// 1: 只是测试连接成功, 并且确定证书存在
		// 2: 验证证书是否正确
		// 3: 测试 HTTP 连接
		// 4: 验证是否是 NoSuchBucket 错误
		// (2.x版默认等级为3, 所以如果lv2搜到的IP不能用, 可以改为 3)
		"Level": 4,
	},

	// 暂时只支持 google IP
	"TLS": {
		// 同 QUIC 说明
		"ScanCountPerIP": 1,
		"ServerName": [],
		"HTTPVerifyHosts": ["dns.google.com"],
		"HandshakeTimeout": 2500,
		"ScanMinRTT": 0,
		"ScanMaxRTT": 3000,
		"RecordLimit": 10000,
		"OutputSeparator": "|",
		"InputFile": "./iprange_tls.txt",
		"OutputFile": "./out_tls.txt",
		// 1: 测试 tls 连接并握手成功
		// 2: 证书验证
		// 3: http 测试
		"Level": 3,
	},

	"SNI": {
		"ScanCountPerIP": 1,
		// 注意, SNI 需要同时验证两个不同域名的 ServerName 才能正确确认
		// 当然你也可以改成一个来简单确认
		// 还有, 不要填封锁严重的域名, 比如 google 的域名
		// 这里的域名最好不用默认的, 防止检测
		"ServerName": ["www.bing.com", "duckduckgo.com"],
		// "HTTPVerifyHosts": ["dns.google.com"],
		"HandshakeTimeout": 2500,
		"ScanMinRTT": 0,
		"ScanMaxRTT": 3000,
		"RecordLimit": 10000,
		"OutputSeparator": "\r\n",
		"InputFile": "./iprange_sni.txt",
		"OutputFile": "./out_sni.txt",
		// 1: 测试 tls 连接并握手成功
		// 2: 证书验证
		// 3: http 测试
		"Level": 2,
	},
}


================================================
FILE: config.user.json.example
================================================
{
	"ScanWorker": 100,
	"VerifyPing": false,
	"ScanMinPingRTT": 80,
	"ScanMaxPingRTT": 800,	
	
	"ScanMode":   "quic",
	
	"Ping": {
		//"ScanCountPerIP":  1,
		//"ScanMinRTT":      0,
		//"ScanMaxRTT":      1000,
		//"RecordLimit":     10000,
		//"OutputSeparator": "\r\n",
		//"InputFile":       "./iprange_ping.txt",
		//"OutputFile":      "./out_ping.txt"
	},

	"QUIC": {
		//"ScanCountPerIP":   1,
		//"ServerName":       [],
		//"HTTPVerifyHosts":  ["dns.google.com"],
		//"HandshakeTimeout": 2500,
		//"ScanMinRTT":       0,
		//"ScanMaxRTT":       3000,
		//"RecordLimit":      10000,
		//"OutputSeparator":  "gop",
		//"InputFile": "./iprange_quic.txt",
		//"OutputFile": "./out_quic.txt",
		//"Level": 3
	},

	"TLS": {
		//"ScanCountPerIP":   1,
		//"ServerName":       [],
		//"HTTPVerifyHosts":  ["dns.google.com"],
		//"HandshakeTimeout": 2500,
		//"ScanMinRTT":       0,
		//"ScanMaxRTT":       3000,
		//"RecordLimit":      10000,
		//"OutputSeparator":  "|",
		//"InputFile":        "./iprange_tls.txt",
		//"OutputFile":       "./out_tls.txt",
		//"Level": 3
	},

	"SNI": {
		//"ScanCountPerIP": 1,
		//"ServerName": ["www.bing.com", "duckduckgo.com"],
		//"HandshakeTimeout": 2500,
		//"ScanMinRTT":       0,
		//"ScanMaxRTT":       3000,
		//"RecordLimit":      10000,
		//"OutputSeparator":  "\r\n",
		//"InputFile":        "./iprange_sni.txt",
		//"OutputFile":       "./out_sni.txt",
		//"Level": 2
	}
}


================================================
FILE: go.mod
================================================
module github.com/kisesy/gscan_quic

go 1.21

require (
	github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721
	github.com/quic-go/quic-go v0.36.1-0.20230701190300-fd0c9bbf9e1f
)

require (
	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
	github.com/golang/mock v1.6.0 // indirect
	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
	github.com/onsi/ginkgo/v2 v2.9.5 // indirect
	github.com/quic-go/qpack v0.4.0 // indirect
	github.com/quic-go/qtls-go1-20 v0.3.0 // indirect
	golang.org/x/crypto v0.4.0 // indirect
	golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
	golang.org/x/mod v0.10.0 // indirect
	golang.org/x/net v0.10.0 // indirect
	golang.org/x/sys v0.8.0 // indirect
	golang.org/x/text v0.9.0 // indirect
	golang.org/x/tools v0.9.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.3.0 h1:NrCXmDl8BddZwO67vlvEpBTwT89bJfKYygxv4HQvuDk=
github.com/quic-go/qtls-go1-20 v0.3.0/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.36.1-0.20230701190300-fd0c9bbf9e1f h1:I4cT4zLUjYfL6SEqIzZQWe1xTmJRvKZRWrLhmcyaZeA=
github.com/quic-go/quic-go v0.36.1-0.20230701190300-fd0c9bbf9e1f/go.mod h1:XtCUOCALTTWbPyd0IxFfHf6h0sEMubRFvEYHl3QxKw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: gscan.go
================================================
package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"time"
)

type ScanConfig struct {
	ScanCountPerIP   int
	ServerName       []string
	HTTPVerifyHosts  []string
	HandshakeTimeout time.Duration
	ScanMinRTT       time.Duration
	ScanMaxRTT       time.Duration
	RecordLimit      int
	InputFile        string
	OutputFile       string
	OutputSeparator  string
	Level            int
}

type GScanner struct {
	ScanWorker     int
	VerifyPing     bool
	ScanMinPingRTT time.Duration
	ScanMaxPingRTT time.Duration
	DisablePause   bool
	EnableBackup   bool
	BackupDir      string

	ScanRecords `json:"-"`

	ScanMode string
	PING     ScanConfig
	QUIC     ScanConfig
	TLS      ScanConfig
	SNI      ScanConfig
}

func init() {
	rand.Seed(time.Now().UnixNano())

	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func (gs *GScanner) loadConfig(cfgFile string) error {
	exe, err := os.Executable()
	if err != nil {
		return errors.New("could not get executable path")
	}
	execFolder := filepath.Dir(exe)

	if strings.HasPrefix(cfgFile, "./") {
		cfgFile = filepath.Join(execFolder, cfgFile)
	}

	config := gs
	if err := readJsonConfig(cfgFile, config); err != nil {
		return fmt.Errorf("could not read config file: %v", err)
	}

	if config.EnableBackup {
		if strings.HasPrefix(config.BackupDir, "./") {
			config.BackupDir = filepath.Join(execFolder, config.BackupDir)
		}
		err := os.MkdirAll(config.BackupDir, 0o644)
		if err != nil {
			return fmt.Errorf("could not create backup dir: %v", err)
		}
	}

	config.ScanMode = strings.ToLower(config.ScanMode)
	if config.ScanMode == "ping" {
		config.VerifyPing = false
	}

	config.ScanMinPingRTT *= time.Millisecond
	config.ScanMaxPingRTT *= time.Millisecond

	scanConfigs := []*ScanConfig{&config.QUIC, &config.TLS, &config.SNI, &config.PING}
	for _, scanConfig := range scanConfigs {
		if strings.HasPrefix(scanConfig.InputFile, "./") {
			scanConfig.InputFile = filepath.Join(execFolder, scanConfig.InputFile)
		} else {
			scanConfig.InputFile, _ = filepath.Abs(scanConfig.InputFile)
		}
		if strings.HasPrefix(scanConfig.OutputFile, "./") {
			scanConfig.OutputFile = filepath.Join(execFolder, scanConfig.OutputFile)
		} else {
			scanConfig.OutputFile, _ = filepath.Abs(scanConfig.OutputFile)
		}
		if !pathExist(scanConfig.InputFile) {
			os.Create(scanConfig.InputFile)
		}

		scanConfig.ScanMinRTT *= time.Millisecond
		scanConfig.ScanMaxRTT *= time.Millisecond
		scanConfig.HandshakeTimeout *= time.Millisecond
	}
	return nil
}

func main() {
	var cfgfile string
	flag.StringVar(&cfgfile, "Config File", "./config.json", "Config file, json format")
	flag.Parse()

	scanner := new(GScanner)

	defer func() {
		if r := recover(); r != nil {
			fmt.Println("panic:", r)
		}
		fmt.Println()
		if scanner.DisablePause {
			return
		}
		if runtime.GOOS == "windows" {
			cmd := exec.Command("cmd", "/C", "pause")
			cmd.Stdout = os.Stdout
			cmd.Stdin = os.Stdin
			// 改为 start, 程序可以正常退出, 这样一些程序监视工具可以正常测到程序结束了
			cmd.Start()
		} else {
			fmt.Println("Press [Enter] to exit...")
			fmt.Scanln()
		}
	}()
	err := scanner.loadConfig(cfgfile)
	if err != nil {
		log.Println(err)
		return
	}

	scanMode := scanner.ScanMode
	cfg, _ := scanner.getScanConfig(scanMode)

	iprangeFile := cfg.InputFile
	if !pathExist(iprangeFile) {
		log.Panicf("IP Range file not exist: %s", iprangeFile)
	}

	log.Printf("Start loading IP Range file: %s", iprangeFile)
	ipqueue, err := parseIPRangeFile(iprangeFile)
	if err != nil {
		log.Panicln(err)
	}

	log.Printf("Start scanning available IP")
	startTime := time.Now()
	scanner.StartScan(ipqueue)

	records := scanner.Records()

	log.Printf("Scanned %d IP in %s, found %d records",
		scanner.ScanCount(), time.Since(startTime), len(records))

	if len(records) == 0 {
		return
	}

	sort.Slice(records, func(i, j int) bool {
		return records[i].RTT < records[j].RTT
	})
	a := make([]string, len(records))
	for i, r := range records {
		a[i] = r.IP
	}
	b := new(bytes.Buffer)
	if cfg.OutputSeparator == "gop" {
		out := strings.Join(a, `", "`)
		b.WriteString(`"`)
		b.WriteString(out)
		b.WriteString(`",`)
	} else {
		out := strings.Join(a, cfg.OutputSeparator)
		b.WriteString(out)
	}

	if err := os.WriteFile(cfg.OutputFile, b.Bytes(), 0o644); err != nil {
		log.Printf("Failed to write output file:%s for reason: %v", cfg.OutputFile, err)
	} else {
		log.Printf("All results written to %s", cfg.OutputFile)
	}

	if scanner.EnableBackup {
		filename := fmt.Sprintf("%s_%s_lv%d.txt", scanMode, time.Now().Format("20060102_150405"), cfg.Level)

		bakfilename := filepath.Join(scanner.BackupDir, filename)
		if err := os.WriteFile(bakfilename, b.Bytes(), 0o644); err != nil {
			log.Printf("Failed to write output file:%s for reason: %v\n", bakfilename, err)
		} else {
			log.Printf("All results written to %s\n", bakfilename)
		}
	}
}

func (gcfg *GScanner) getScanConfig(scanMode string) (*ScanConfig, testIPFunc) {
	switch scanMode {
	case "quic":
		return &gcfg.QUIC, testQuic
	case "tls":
		return &gcfg.TLS, testTls
	case "sni":
		return &gcfg.SNI, testSni
	case "ping":
		return &gcfg.PING, testPing
	// case "socks5":
	// testIPFunc = testSocks5
	default:
		log.Panicln("Unknown scan mode:", scanMode)
	}
	return nil, nil
}


================================================
FILE: iprange.go
================================================
package main

import (
	"bufio"
	"math/rand"
	"net"
	"os"
	"sort"
	"strings"

	"github.com/mikioh/ipaddr"
)

// 从每组地址中分离出起始IP以及结束IP
// 参考自 moonshawdo 的代码
func splitIP(strline string) []ipaddr.Prefix {
	var begin, end string
	if strings.Contains(strline, "-") && strings.Contains(strline, "/") {
		ss := strings.Split(strline, "-")
		if len(ss) == 2 {
			iprange1, iprange2 := ss[0], ss[1]
			// "1.9.22.0/24-1.9.22.0"
			if strings.Contains(iprange1, "/") && !strings.Contains(iprange2, "/") {
				begin = iprange1[:strings.Index(iprange1, "/")]
				if begin == iprange2 {
					iprange2 = iprange1
				}
			} else if strings.Contains(iprange2, "/") {
				// 1.9.22.0/24-1.9.22.0/24
				begin = iprange1[:strings.Index(iprange1, "/")]
			} else {
				// 1.9.22.0-1.9.23.0/24
				begin = iprange1
			}
			// c, err := ipaddr.Parse(begin + "," + iprange2)
			// if err != nil {
			// 	panic(err)
			// }
			// return ipaddr.Aggregate(c.List())

			if c, err := ipaddr.Parse(iprange2); err == nil {
				end = c.Last().IP.String()
			}
		}
	} else if strings.Contains(strline, "-") {
		num_regions := strings.Split(strline, ".")
		if len(num_regions) == 4 {
			// "xxx.xxx.xxx-xxx.xxx-xxx"
			for _, region := range num_regions {
				if strings.Contains(region, "-") {
					a := strings.Split(region, "-")
					s, e := a[0], a[1]
					begin += "." + s
					end += "." + e
				} else {
					begin += "." + region
					end += "." + region
				}
			}
			begin = begin[1:]
			end = end[1:]
		} else {
			// "xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx"
			a := strings.Split(strline, "-")
			begin, end = a[0], a[1]
			if 1 <= len(end) && len(end) <= 3 {
				prefix := begin[0:strings.LastIndex(begin, ".")]
				end = prefix + "." + end
			}
		}
	} else if strings.HasSuffix(strline, ".") {
		// "xxx.xxx.xxx."
		begin = strline + "0"
		end = strline + "255"
	} else if strings.Contains(strline, "/") {
		// "xxx.xxx.xxx.xxx/xx"
		if c, err := ipaddr.Parse(strline); err == nil {
			return c.List()
		}
		return nil
	} else {
		// "xxx.xxx.xxx.xxx"
		// 如果IP带有端口, 那么就分离端口
		if i := strings.LastIndex(strline, ":"); i != -1 {
			if c, err := ipaddr.Parse(strline[:i]); err == nil {
				return c.List()
			}
		}
		begin = strline
		end = strline
	}

	return ipaddr.Summarize(net.ParseIP(begin), net.ParseIP(end))
}

var sepReplacer = strings.NewReplacer(`","`, ",", `", "`, ",", "|", ",")

func parseIPRangeFile(file string) (chan string, error) {
	f, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	ipranges := make([]ipaddr.Prefix, 0)
	scanner := bufio.NewScanner(f)
	// 一行最大 4MB
	buf := make([]byte, 1024*1024*4)
	scanner.Buffer(buf, len(buf))

	for scanner.Scan() {
		line := strings.TrimFunc(scanner.Text(), func(r rune) bool {
			switch r {
			case ',', '|', '"', '\'':
				return true
			case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0:
				return true
			}
			return false
		})
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		// 支持 gop 的 "xxx","xxx" 和 goa 的 xxx|xxx 格式
		if s := sepReplacer.Replace(line); strings.Contains(s, ",") {
			if c, err := ipaddr.Parse(s); err == nil {
				ipranges = append(ipranges, c.List()...)
			}
		} else {
			ipranges = append(ipranges, splitIP(line)...)
		}
	}

	out := make(chan string, 200)
	go func() {
		defer close(out)
		if len(ipranges) > 0 {
			ipranges = dedup(ipranges)

			// 打乱IP段扫描顺序
			rand.Shuffle(len(ipranges), func(i, j int) {
				ipranges[i], ipranges[j] = ipranges[j], ipranges[i]
			})

			// 打乱IP的扫描顺序, 不再等待一个IP段扫描完毕再进行下一个, 提高扫描的随机性
			// 有时候可以更快的扫到IP
			n := 15
			if len(ipranges) < n {
				n = len(ipranges)
			}
			ops(len(ipranges), n, func(i, _ int) {
				c := ipaddr.NewCursor([]ipaddr.Prefix{ipranges[i]})
				for ip := c.First(); ip != nil; ip = c.Next() {
					out <- ip.IP.String()
				}
			})
		}
	}()
	return out, nil
}

/*
IP段去重	(此描述对当前算法不适用-2017/09/21)

"1.9.22.0-255"
"1.9.0.0/16"
"1.9.22.0-255"
"1.9.22.0/24"
"1.9.22.0-255"
"1.9.22.0-1.9.22.100"
"1.9.22.0-1.9.22.255"
"1.9.0.0/16"
"3.3.3.0/24"
"3.3.0.0/16"
"3.3.3.0-255"
"1.1.1.0/24"
"1.9.0.0/16"
"2001:db8::1/128"

	|
	|
	v

[1.9.0.0/16 3.3.0.0/16 1.1.1.0/24 203.0.113.0/24 2001:db8::1/128]
*/
func dedup(s []ipaddr.Prefix) []ipaddr.Prefix {
	sort.Slice(s, func(i int, j int) bool {
		return s[i].String() < s[j].String()
	})
	out := s[:1]
	t := s[0]
	for _, s := range s[1:] {
		if !t.Contains(&s) && !t.Equal(&s) {
			out = append(out, s)
			t = s
		}
	}
	return out
}


================================================
FILE: iprange_quic.txt.example
================================================
108.177.97.0/24
113.171.202.0/24
113.171.216.0/24
113.171.220.0/24
113.171.232.0/24
113.171.236.0/24
113.171.237.0/24
113.171.238.0/24
121.158.53.0/24
121.78.42.0/24
122.202.129.0/24
123.241.255.0/24
124.216.0.0/24
157.197.92.0/24
157.197.93.0/24
182.163.240.0/24
183.91.238.0/24
203.233.10.0/24
203.233.37.0/24
203.233.88.0/24
203.233.92.0/24
203.233.96.0/24
203.248.132.0/24
203.252.15.0/24
211.49.146.0/24
221.144.116.0/24
223.62.225.0/24
49.231.113.0/24
61.62.98.0/24
61.90.179.0/24
61.90.189.0/24
61.91.8.0/24
61.91.9.0/24
222.251.134.0/24

================================================
FILE: ping.go
================================================
package main

// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// taken from http://golang.org/src/pkg/net/ipraw_test.go

import (
	"bytes"
	"context"
	"errors"
	"net"
	"os"
	"time"
)

func testPing(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool {
	start := time.Now()
	if err := Pinger(ip, config.ScanMaxRTT); err != nil {
		return false
	}
	if rtt := time.Since(start); rtt > config.ScanMinRTT {
		record.RTT += rtt
		return true
	}
	return false
}

const (
	icmpv4EchoRequest = 8
	icmpv4EchoReply   = 0
	icmpv6EchoRequest = 128
	icmpv6EchoReply   = 129
)

var ErrPingConnFailed = errors.New("ping: connect failed")

type icmpMessage struct {
	Type     int             // type
	Code     int             // code
	Checksum int             // checksum
	Body     icmpMessageBody // body
}

type icmpMessageBody interface {
	Len() int
	Marshal() ([]byte, error)
}

// Marshal returns the binary enconding of the ICMP echo request or
// reply message m.
func (m *icmpMessage) Marshal() ([]byte, error) {
	b := []byte{byte(m.Type), byte(m.Code), 0, 0}
	if m.Body != nil && m.Body.Len() != 0 {
		mb, err := m.Body.Marshal()
		if err != nil {
			return nil, err
		}
		b = append(b, mb...)
	}
	switch m.Type {
	case icmpv6EchoRequest, icmpv6EchoReply:
		return b, nil
	}
	csumcv := len(b) - 1 // checksum coverage
	s := uint32(0)
	for i := 0; i < csumcv; i += 2 {
		s += uint32(b[i+1])<<8 | uint32(b[i])
	}
	if csumcv&1 == 0 {
		s += uint32(b[csumcv])
	}
	s = s>>16 + s&0xffff
	s = s + s>>16
	// Place checksum back in header; using ^= avoids the
	// assumption the checksum bytes are zero.
	b[2] ^= byte(^s & 0xff)
	b[3] ^= byte(^s >> 8)
	return b, nil
}

// parseICMPMessage parses b as an ICMP message.
func parseICMPMessage(b []byte) (*icmpMessage, error) {
	msglen := len(b)
	if msglen < 4 {
		return nil, errors.New("message too short")
	}
	m := &icmpMessage{Type: int(b[0]), Code: int(b[1]), Checksum: int(b[2])<<8 | int(b[3])}
	if msglen > 4 {
		var err error
		switch m.Type {
		case icmpv4EchoRequest, icmpv4EchoReply, icmpv6EchoRequest, icmpv6EchoReply:
			m.Body, err = parseICMPEcho(b[4:])
			if err != nil {
				return nil, err
			}
		}
	}
	return m, nil
}

// imcpEcho represenets an ICMP echo request or reply message body.
type icmpEcho struct {
	ID   int    // identifier
	Seq  int    // sequence number
	Data []byte // data
}

func (p *icmpEcho) Len() int {
	if p == nil {
		return 0
	}
	return 4 + len(p.Data)
}

// Marshal returns the binary enconding of the ICMP echo request or
// reply message body p.
func (p *icmpEcho) Marshal() ([]byte, error) {
	b := make([]byte, 4+len(p.Data))
	b[0], b[1] = byte(p.ID>>8), byte(p.ID&0xff)
	b[2], b[3] = byte(p.Seq>>8), byte(p.Seq&0xff)
	copy(b[4:], p.Data)
	return b, nil
}

// parseICMPEcho parses b as an ICMP echo request or reply message body.
func parseICMPEcho(b []byte) (*icmpEcho, error) {
	bodylen := len(b)
	p := &icmpEcho{ID: int(b[0])<<8 | int(b[1]), Seq: int(b[2])<<8 | int(b[3])}
	if bodylen > 4 {
		p.Data = make([]byte, bodylen-4)
		copy(p.Data, b[4:])
	}
	return p, nil
}

func Ping(address string, timeout time.Duration) error {
	return Pinger(address, timeout)
}

func Pinger(address string, timeout time.Duration) error {
	typ := icmpv4EchoRequest
	network := "ip4:icmp"

	isIpv6 := false
	if ip := net.ParseIP(address); ip != nil && ip.To4() == nil {
		typ = icmpv6EchoRequest
		network = "ip6:ipv6-icmp"
		isIpv6 = true
	}

	c, err := net.Dial(network, address)
	if err != nil {
		return ErrPingConnFailed
	}
	defer c.Close()
	deadline := time.Now().Add(timeout)
	c.SetReadDeadline(deadline)

	xid, xseq := os.Getpid()&0xffff, 1
	wb, err := (&icmpMessage{
		Type: typ, Code: 0,
		Body: &icmpEcho{
			ID: xid, Seq: xseq,
			Data: bytes.Repeat([]byte("Go Go Gadget Ping!!!"), 3),
		},
	}).Marshal()
	if err != nil {
		return err
	}
	if _, err = c.Write(wb); err != nil {
		return err
	}
	var m *icmpMessage
	rb := make([]byte, 20+len(wb))
	for {
		// read_timeout := deadline.Sub(time.Now())
		// c.SetReadDeadline(time.Now().Add(read_timeout))
		if _, err = c.Read(rb); err != nil {
			return err
		}
		if !isIpv6 {
			rb = ipv4Payload(rb)
		}
		if m, err = parseICMPMessage(rb); err != nil {
			return err
		}
		switch m.Type {
		case icmpv4EchoRequest, icmpv6EchoRequest:
			continue
		}
		break
	}
	return nil
}

func ipv4Payload(b []byte) []byte {
	if len(b) < 20 {
		return b
	}
	hdrlen := int(b[0]&0x0f) << 2
	return b[hdrlen:]
}


================================================
FILE: quic.go
================================================
package main

import (
	"bytes"
	"context"
	"crypto/tls"
	"io"
	"math/rand"
	"net"
	"net/http"
	"strings"
	"time"

	quic "github.com/quic-go/quic-go"
	"github.com/quic-go/quic-go/http3"
)

var errNoSuchBucket = []byte("<?xml version='1.0' encoding='UTF-8'?><Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist.</Message></Error>")

func testQuic(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool {
	start := time.Now()

	quicCfg := &quic.Config{
		HandshakeIdleTimeout: config.HandshakeTimeout,
		KeepAlivePeriod:      0,
	}

	serverName := ""
	if len(config.ServerName) == 0 {
		serverName = randomHost()
	} else {
		serverName = randomChoice(config.ServerName)
	}

	tlsCfg := &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         serverName,
		NextProtos:         []string{"h3-29", "h3", "hq", "quic"},
	}

	ctx, cancel := context.WithTimeout(ctx, config.ScanMaxRTT)
	defer cancel()

	quicConn, err := quic.DialAddrEarly(ctx, net.JoinHostPort(ip, "443"), tlsCfg, quicCfg)
	if err != nil {
		return false
	}
	defer quicConn.CloseWithError(0, "")

	// lv1 只会验证证书是否存在
	cs := quicConn.ConnectionState().TLS
	if !cs.HandshakeComplete || len(cs.PeerCertificates) < 2 {
		return false
	}

	// lv2 验证证书是否正确
	if config.Level > 1 {
		pkp := cs.PeerCertificates[1].RawSubjectPublicKeyInfo
		if !bytes.Equal(gpkp, pkp) {
			return false
		}
	}

	// lv3 使用 http 访问来验证
	if config.Level > 2 {
		tr := &http3.RoundTripper{DisableCompression: true}
		defer tr.Close()
		tr.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
			return quicConn, err
		}
		// 设置超时
		hclient := &http.Client{
			Transport: tr,
			CheckRedirect: func(req *http.Request, via []*http.Request) error {
				return http.ErrUseLastResponse
			},
			Timeout: config.ScanMaxRTT - time.Since(start),
		}
		url := "https://" + config.HTTPVerifyHosts[rand.Intn(len(config.HTTPVerifyHosts))]
		req, _ := http.NewRequest(http.MethodGet, url, nil)
		req.Close = true
		resp, _ := hclient.Do(req)
		if resp == nil || (resp.StatusCode < 200 || resp.StatusCode >= 400) || !strings.Contains(resp.Header.Get("Alt-Svc"), `quic=":443"`) {
			return false
		}
		if resp.Body != nil {
			defer resp.Body.Close()
			// lv4 验证是否是 NoSuchBucket 错误
			if config.Level > 3 && resp.Header.Get("Content-Type") == "application/xml; charset=UTF-8" { // 也许条件改为 || 更好
				body, err := io.ReadAll(resp.Body)
				if err != nil || bytes.Equal(body, errNoSuchBucket) {
					return false
				}
			} else {
				io.Copy(io.Discard, resp.Body)
			}
		}
	}

	if rtt := time.Since(start); rtt > config.ScanMinRTT {
		record.RTT += rtt
		return true
	}
	return false
}


================================================
FILE: scan.go
================================================
package main

import (
	"context"
	"log"
	"os"
	"os/signal"
	"sync"
	"sync/atomic"
	"time"
)

type ScanRecord struct {
	IP  string
	RTT time.Duration
}

type ScanRecords struct {
	recordMutex sync.Mutex
	records     []*ScanRecord
	scanCounter int32
}

func (srs *ScanRecords) AddRecord(rec *ScanRecord) {
	srs.recordMutex.Lock()
	srs.records = append(srs.records, rec)
	srs.recordMutex.Unlock()
	log.Printf("Found a record: IP=%s, RTT=%s\n", rec.IP, rec.RTT.String())
}

func (srs *ScanRecords) IncScanCounter() {
	scanCount := atomic.AddInt32(&srs.scanCounter, 1)
	if scanCount%1000 == 0 {
		log.Printf("Scanned %d IPs, Found %d records\n", scanCount, srs.RecordSize())
	}
}

func (srs *ScanRecords) RecordSize() int {
	srs.recordMutex.Lock()
	defer srs.recordMutex.Unlock()
	return len(srs.records)
}

func (srs *ScanRecords) ScanCount() int32 {
	return atomic.LoadInt32(&srs.scanCounter)
}

func (srs *ScanRecords) Records() []*ScanRecord {
	srs.recordMutex.Lock()
	defer srs.recordMutex.Unlock()
	return srs.records
}

type testIPFunc func(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool

func testip(ctx context.Context, testFunc testIPFunc, ip string, config *ScanConfig) *ScanRecord {
	record := new(ScanRecord)
	for i := 0; i < config.ScanCountPerIP; i++ {
		if !testFunc(ctx, ip, config, record) {
			return nil
		}
	}
	record.IP = ip
	record.RTT = record.RTT / time.Duration(config.ScanCountPerIP)
	return record
}

func (gs *GScanner) testIPWorker(ctx context.Context, ipQueue chan string) {
	cfg, testFunc := gs.getScanConfig(gs.ScanMode)

	for ip := range ipQueue {
		// log.Printf("Start testing IP: %s", ip)

		if gs.VerifyPing {
			start := time.Now()

			pingErr := Ping(ip, gs.ScanMaxPingRTT)
			if pingErr != nil || time.Since(start) < gs.ScanMinPingRTT {
				continue
			}
		}

		select {
		case <-ctx.Done():
			return
		default:
			r := testip(ctx, testFunc, ip, cfg)
			if r != nil {
				gs.AddRecord(r) // 这里放到前面,扫描时可能会多出一些记录, 但是不影响
				if gs.RecordSize() >= cfg.RecordLimit {
					return
				}
			}
			gs.IncScanCounter() // 扫描完后才增加计数
		}

	}
}

func (gs *GScanner) StartScan(ipQueue chan string) {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	n := gs.ScanWorker
	ops(n, n, func(i, thread int) {
		gs.testIPWorker(ctx, ipQueue)
	})
}


================================================
FILE: sni.go
================================================
package main

import (
	"context"
	"crypto/tls"
	"net"
	"net/http"
	"net/http/httputil"
	"time"
)

func testSni(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool {
	tlscfg := &tls.Config{
		InsecureSkipVerify: true,
	}

	for _, serverName := range config.ServerName {
		start := time.Now()

		ctx, cancel := context.WithTimeout(ctx, config.ScanMaxRTT)
		defer cancel()

		conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", net.JoinHostPort(ip, "443"))
		if err != nil {
			return false
		}

		tlscfg.ServerName = serverName
		tlsconn := tls.Client(conn, tlscfg)
		tlsconn.SetDeadline(time.Now().Add(config.HandshakeTimeout))
		if err = tlsconn.Handshake(); err != nil {
			tlsconn.Close()
			return false
		}
		if config.Level > 1 {
			pcs := tlsconn.ConnectionState().PeerCertificates
			if len(pcs) == 0 || pcs[0].Subject.CommonName != serverName {
				tlsconn.Close()
				return false
			}
		}
		if config.Level > 2 {
			req, err := http.NewRequest(http.MethodHead, "https://"+serverName, nil)
			if err != nil {
				tlsconn.Close()
				return false
			}
			tlsconn.SetDeadline(time.Now().Add(config.ScanMaxRTT - time.Since(start)))
			resp, err := httputil.NewClientConn(tlsconn, nil).Do(req)
			if err != nil {
				tlsconn.Close()
				return false
			}
			// io.Copy(os.Stdout, resp.Body)
			// if resp.Body != nil {
			// 	io.Copy(io.Discard, resp.Body)
			// 	resp.Body.Close()
			// }
			if resp.StatusCode >= 400 {
				tlsconn.Close()
				return false
			}
		}

		tlsconn.Close()

		rtt := time.Since(start)
		if rtt < config.ScanMinRTT {
			return false
		}
		record.RTT += rtt
	}
	return true
}


================================================
FILE: tls.go
================================================
package main

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/base64"
	"io"
	"math/rand"
	"net"
	"net/http"
	"time"
)

var gpkp, _ = base64.StdEncoding.DecodeString("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9Yjf52KMHjf4N0KQf2yH0PtlgiX96MtrpP9t6Voj4pn2HOmSA5kTfAkKivpC1l5WJKp6M4Qf0elpu7l07FdMZmiTdzdVU/45EE23NLtfJXc3OxeU6jzlndW8w7RD6y6nR++wRBFj2LRBhd1BMEiTG7+39uBFAiHglkIXz9krZVY0ByYEDaj9fcou7+pIfDdNPwCfg9/vdYQueVdc/FduGpb//Iyappm+Jdl/liwG9xEqAoCA62MYPFBJh+WKyl8ZK1mWgQCg+1HbyncLC8mWT+9wScdcbSD9mbS04soud/0t3Au2axMMjBkrF5aYufCL9qAnu7bjjVGPva7Hm7GJnQIDAQAB")

func testTls(ctx context.Context, ip string, config *ScanConfig, record *ScanRecord) bool {
	start := time.Now()

	ctx, cancel := context.WithTimeout(ctx, config.ScanMaxRTT)
	defer cancel()

	var dialer net.Dialer
	conn, err := dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip, "443"))
	if err != nil {
		return false
	}
	defer conn.Close()

	var serverName string
	if len(config.ServerName) == 0 {
		serverName = randomHost()
	} else {
		serverName = randomChoice(config.ServerName)
	}

	tlscfg := &tls.Config{
		InsecureSkipVerify: true,
		MinVersion:         tls.VersionTLS11,
		MaxVersion:         tls.VersionTLS12,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
			tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
			tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
		},
		ServerName: serverName,
	}

	tlsconn := tls.Client(conn, tlscfg)
	defer tlsconn.Close()

	tlsconn.SetDeadline(time.Now().Add(config.HandshakeTimeout))
	if err = tlsconn.Handshake(); err != nil {
		return false
	}
	if config.Level > 1 {
		pcs := tlsconn.ConnectionState().PeerCertificates
		if pcs == nil || len(pcs) < 2 {
			return false
		}
		if org := pcs[1].Subject.Organization; len(org) == 0 || org[0] != "Google Trust Services LLC" {
			return false
		}
		pkp := pcs[1].RawSubjectPublicKeyInfo
		if !bytes.Equal(gpkp, pkp) {
			return false
		}
	}
	if config.Level > 2 {
		url := "https://" + config.HTTPVerifyHosts[rand.Intn(len(config.HTTPVerifyHosts))]
		req, _ := http.NewRequest(http.MethodGet, url, nil)
		req.Close = true
		c := http.Client{
			Transport: &http.Transport{
				DialTLS: func(network, addr string) (net.Conn, error) { return tlsconn, nil },
			},
			CheckRedirect: func(req *http.Request, via []*http.Request) error {
				return http.ErrUseLastResponse
			},
			Timeout: config.ScanMaxRTT - time.Since(start),
		}
		resp, _ := c.Do(req)
		if resp == nil || (resp.StatusCode < 200 || resp.StatusCode >= 400) {
			return false
		}
		if resp.Body != nil {
			io.Copy(io.Discard, resp.Body)
			resp.Body.Close()
		}
	}

	if rtt := time.Since(start); rtt > config.ScanMinRTT {
		record.RTT += rtt
		return true
	}
	return false
}


================================================
FILE: util.go
================================================
package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"math/rand"
	"os"
	"path"
	"strings"
	"sync"
)

// 代码来自: goproxy
func readJsonConfig(filename string, config interface{}) error {
	fileext := path.Ext(filename)
	filename1 := strings.TrimSuffix(filename, fileext) + ".user" + fileext

	cm := make(map[string]interface{})
	for i, name := range []string{filename, filename1} {
		data, err := os.ReadFile(name)
		if err != nil {
			if i == 0 {
				return err
			} else {
				continue
			}
		}
		data = bytes.TrimPrefix(data, []byte("\xef\xbb\xbf"))
		data, err = readJson(bytes.NewReader(data))
		if err != nil {
			return err
		}

		cm1 := make(map[string]interface{})

		d := json.NewDecoder(bytes.NewReader(data))
		d.UseNumber()

		if err = d.Decode(&cm1); err != nil {
			return err
		}

		if err = mergeMap(cm, cm1); err != nil {
			return err
		}
	}

	data, err := json.Marshal(cm)
	if err != nil {
		return err
	}

	d := json.NewDecoder(bytes.NewReader(data))
	d.UseNumber()

	return d.Decode(config)
}

func readJson(r io.Reader) ([]byte, error) {
	scanner := bufio.NewScanner(r)
	var b bytes.Buffer
	prev := ""
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "//") {
			continue
		}
		if strings.HasPrefix(line, "}") || strings.HasPrefix(line, "]") {
			prev = strings.TrimSuffix(prev, ",")
		}

		b.WriteString(prev)
		prev = line
	}
	b.WriteString(prev)
	return b.Bytes(), scanner.Err()
}

func mergeMap(m1 map[string]interface{}, m2 map[string]interface{}) error {
	for key, value := range m2 {

		m1v, m1_has_key := m1[key]
		m2v, m2v_is_map := value.(map[string]interface{})
		m1v1, m1v_is_map := m1v.(map[string]interface{})

		switch {
		case !m1_has_key, !m2v_is_map:
			m1[key] = value
		case !m1v_is_map:
			return fmt.Errorf("m1v=%#v is not a map, but m2v=%#v is a map", m1v, m2v)
		default:
			mergeMap(m1v1, m2v)
		}
	}

	return nil
}

func randInt(l, u int) int {
	return rand.Intn(u-l) + l
}

func randomChoice[T any](a []T) T {
	return a[rand.Intn(len(a))]
}

// 生成两段或三段的随机字符串当作 host
// llm.xadl
// unupk.bfrf.pvi
func randomHost() string {
	a := make([][]byte, randInt(2, 4))
	for i := range a {
		m := randInt(3, 7)
		b := make([]byte, m)
		for j := 0; j < m; j++ {
			b[j] = byte(randInt(97, 122))
		}
		a[i] = b
	}
	return string(bytes.Join(a, []byte{46}))
}

func or[T comparable](vals ...T) T {
	var zero T
	for _, val := range vals {
		if val != zero {
			return val
		}
	}
	return zero
}

// pathExist 返回文件或文件夹是否存在
func pathExist(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

func ops(count, threads int, op func(i, thread int)) {
	var wg sync.WaitGroup
	wg.Add(threads)
	for i := 0; i < threads; i++ {
		s, e := count/threads*i, count/threads*(i+1)
		if i == threads-1 {
			e = count
		}
		go func(i, s, e int) {
			for j := s; j < e; j++ {
				op(j, i)
			}
			wg.Done()
		}(i, s, e)
	}
	wg.Wait()
}
Download .txt
gitextract_kl6s_cna/

├── .github/
│   └── workflows/
│       ├── go.yml
│       └── release.yml
├── .gitignore
├── .goreleaser.yaml
├── .travis.yml
├── LICENSE
├── README.md
├── config.json
├── config.user.json.example
├── go.mod
├── go.sum
├── gscan.go
├── iprange.go
├── iprange_quic.txt.example
├── ping.go
├── quic.go
├── scan.go
├── sni.go
├── tls.go
└── util.go
Download .txt
SYMBOL INDEX (48 symbols across 8 files)

FILE: gscan.go
  type ScanConfig (line 19) | type ScanConfig struct
  type GScanner (line 33) | type GScanner struct
    method loadConfig (line 57) | func (gs *GScanner) loadConfig(cfgFile string) error {
    method getScanConfig (line 209) | func (gcfg *GScanner) getScanConfig(scanMode string) (*ScanConfig, tes...
  function init (line 51) | func init() {
  function main (line 114) | func main() {

FILE: iprange.go
  function splitIP (line 16) | func splitIP(strline string) []ipaddr.Prefix {
  function parseIPRangeFile (line 98) | func parseIPRangeFile(file string) (chan string, error) {
  function dedup (line 187) | func dedup(s []ipaddr.Prefix) []ipaddr.Prefix {

FILE: ping.go
  function testPing (line 18) | func testPing(ctx context.Context, ip string, config *ScanConfig, record...
  constant icmpv4EchoRequest (line 31) | icmpv4EchoRequest = 8
  constant icmpv4EchoReply (line 32) | icmpv4EchoReply   = 0
  constant icmpv6EchoRequest (line 33) | icmpv6EchoRequest = 128
  constant icmpv6EchoReply (line 34) | icmpv6EchoReply   = 129
  type icmpMessage (line 39) | type icmpMessage struct
    method Marshal (line 53) | func (m *icmpMessage) Marshal() ([]byte, error) {
  type icmpMessageBody (line 46) | type icmpMessageBody interface
  function parseICMPMessage (line 84) | func parseICMPMessage(b []byte) (*icmpMessage, error) {
  type icmpEcho (line 104) | type icmpEcho struct
    method Len (line 110) | func (p *icmpEcho) Len() int {
    method Marshal (line 119) | func (p *icmpEcho) Marshal() ([]byte, error) {
  function parseICMPEcho (line 128) | func parseICMPEcho(b []byte) (*icmpEcho, error) {
  function Ping (line 138) | func Ping(address string, timeout time.Duration) error {
  function Pinger (line 142) | func Pinger(address string, timeout time.Duration) error {
  function ipv4Payload (line 198) | func ipv4Payload(b []byte) []byte {

FILE: quic.go
  function testQuic (line 20) | func testQuic(ctx context.Context, ip string, config *ScanConfig, record...

FILE: scan.go
  type ScanRecord (line 13) | type ScanRecord struct
  type ScanRecords (line 18) | type ScanRecords struct
    method AddRecord (line 24) | func (srs *ScanRecords) AddRecord(rec *ScanRecord) {
    method IncScanCounter (line 31) | func (srs *ScanRecords) IncScanCounter() {
    method RecordSize (line 38) | func (srs *ScanRecords) RecordSize() int {
    method ScanCount (line 44) | func (srs *ScanRecords) ScanCount() int32 {
    method Records (line 48) | func (srs *ScanRecords) Records() []*ScanRecord {
  type testIPFunc (line 54) | type testIPFunc
  function testip (line 56) | func testip(ctx context.Context, testFunc testIPFunc, ip string, config ...
  method testIPWorker (line 68) | func (gs *GScanner) testIPWorker(ctx context.Context, ipQueue chan strin...
  method StartScan (line 100) | func (gs *GScanner) StartScan(ipQueue chan string) {

FILE: sni.go
  function testSni (line 12) | func testSni(ctx context.Context, ip string, config *ScanConfig, record ...

FILE: tls.go
  function testTls (line 17) | func testTls(ctx context.Context, ip string, config *ScanConfig, record ...

FILE: util.go
  function readJsonConfig (line 17) | func readJsonConfig(filename string, config interface{}) error {
  function readJson (line 62) | func readJson(r io.Reader) ([]byte, error) {
  function mergeMap (line 82) | func mergeMap(m1 map[string]interface{}, m2 map[string]interface{}) error {
  function randInt (line 102) | func randInt(l, u int) int {
  function randomChoice (line 106) | func randomChoice[T any](a []T) T {
  function randomHost (line 113) | func randomHost() string {
  function or (line 126) | func or[T comparable](vals ...T) T {
  function pathExist (line 137) | func pathExist(name string) bool {
  function ops (line 142) | func ops(count, threads int, op func(i, thread int)) {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (54K chars).
[
  {
    "path": ".github/workflows/go.yml",
    "chars": 598,
    "preview": "# This workflow will build a golang project\n# For more information see: https://docs.github.com/en/actions/automating-bu"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 941,
    "preview": "name: goreleaser\n\non:\n  push:\n    # run only against tags\n    tags:\n      - \"*\"\n\npermissions:\n  contents: write\n  # pack"
  },
  {
    "path": ".gitignore",
    "chars": 321,
    "preview": "# 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"
  },
  {
    "path": ".goreleaser.yaml",
    "chars": 180,
    "preview": "archives:\n  - id: \"gscan_quic\"\n    format_overrides:\n      - goos: windows\n        format: zip\n    files:\n      - LICENS"
  },
  {
    "path": ".travis.yml",
    "chars": 1570,
    "preview": "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\nscr"
  },
  {
    "path": "LICENSE",
    "chars": 1210,
    "preview": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, c"
  },
  {
    "path": "README.md",
    "chars": 1605,
    "preview": "\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"
  },
  {
    "path": "config.json",
    "chars": 2525,
    "preview": "{\n\t// 注意:\n\t// [扫描并发数] 理论上设置越大扫的越快, 但是并不意味越大就可以扫到更多IP\n\t// 大量的并发, 会造成网络堵塞, 甚至触到系统网络的限制或造成路由器宕机而出现更多的问题\n\t// 所以如果在你那里基本扫不到IP"
  },
  {
    "path": "config.user.json.example",
    "chars": 1423,
    "preview": "{\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"
  },
  {
    "path": "go.mod",
    "chars": 828,
    "preview": "module github.com/kisesy/gscan_quic\n\ngo 1.21\n\nrequire (\n\tgithub.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721\n\tgi"
  },
  {
    "path": "go.sum",
    "chars": 7528,
    "preview": "github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0"
  },
  {
    "path": "gscan.go",
    "chars": 5290,
    "preview": "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"
  },
  {
    "path": "iprange.go",
    "chars": 4435,
    "preview": "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// 从每组地址中分离"
  },
  {
    "path": "iprange_quic.txt.example",
    "chars": 577,
    "preview": "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"
  },
  {
    "path": "ping.go",
    "chars": 4554,
    "preview": "package main\n\n// Copyright 2009 The Go Authors.  All rights reserved.\n// Use of this source code is governed by a BSD-st"
  },
  {
    "path": "quic.go",
    "chars": 2817,
    "preview": "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\"t"
  },
  {
    "path": "scan.go",
    "chars": 2330,
    "preview": "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\t"
  },
  {
    "path": "sni.go",
    "chars": 1709,
    "preview": "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 testS"
  },
  {
    "path": "tls.go",
    "chars": 2797,
    "preview": "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/ht"
  },
  {
    "path": "util.go",
    "chars": 3103,
    "preview": "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"
  }
]

About this extraction

This page contains the full source code of the Kisesy/gscan_quic GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (45.3 KB), approximately 18.2k tokens, and a symbol index with 48 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!