├── go.mod ├── README.md ├── LICENSE ├── go.sum ├── .github └── workflows │ └── fetch-and-release.yml └── main.go /go.mod: -------------------------------------------------------------------------------- 1 | module cf-tools 2 | 3 | go 1.24.5 4 | 5 | require ( 6 | github.com/flynn/noise v1.1.0 7 | golang.org/x/crypto v0.40.0 8 | ) 9 | 10 | require golang.org/x/sys v0.34.0 // indirect 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudFlare Tools 2 | 3 | This Go script processes and filters IPv4 prefixes associated with CloudFlare. It converts these prefixes into `/24` blocks and checks if they belong to the CloudFlare CDN or WARP service. 4 | 5 | ## Features 6 | 7 | - Fetches Cloudflare IPv4 BGP prefixes from bgp.tools. 8 | - Converts filtered prefixes into `/24` blocks. 9 | - Checks prefixes for CloudFlare CDN or WARP services. 10 | - Outputs results to specified files. 11 | 12 | ## Usage 13 | 14 | ### Flags 15 | 16 | - `-h, --help`: Show help. 17 | - `-f, --fetch`: Fetch and convert to /24 only. 18 | - `-c, --cdn`: Run the CDN checker. 19 | - `-w, --warp`: Run the WARP checker. 20 | - `-o, --output`: Specify the output file name. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 CompassVPN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= 2 | github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= 3 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 4 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 5 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 6 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 7 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 8 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 9 | golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= 10 | golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 11 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 12 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 13 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 14 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 15 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 16 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 17 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 18 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 19 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 20 | -------------------------------------------------------------------------------- /.github/workflows/fetch-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Fetch And Release 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' # Run daily at midnight (UTC) 6 | workflow_dispatch: 7 | 8 | jobs: 9 | fetch-and-release: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | env: 14 | CGO_ENABLED: 0 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: '1.24.5' 24 | check-latest: true 25 | 26 | - name: Tidy Go modules 27 | run: go mod tidy 28 | 29 | - name: Run Fetch and Convert 30 | run: go run . -f 31 | 32 | - name: Run CDN Checker 33 | run: go run . -c 34 | 35 | - name: Run WARP Checker 36 | run: go run . -w 37 | 38 | - name: Set release variables 39 | run: | 40 | echo "RELEASE_TAG=$(date +%Y%m%d%H%M)" >> $GITHUB_ENV 41 | echo "RELEASE_DATE=$(date -u +'%A %F %T %Z')" >> $GITHUB_ENV 42 | 43 | - name: Create GitHub Release 44 | uses: softprops/action-gh-release@v2 45 | with: 46 | name: ${{ env.RELEASE_TAG }} 47 | tag_name: ${{ env.RELEASE_TAG }} 48 | draft: false 49 | prerelease: false 50 | files: | 51 | all_cf_v4.txt 52 | all_cdn_v4.txt 53 | all_warp_v4.txt 54 | body: | 55 | Auto Updated at ${{ env.RELEASE_DATE }} 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | - name: Clean older releases 60 | uses: dev-drprasad/delete-older-releases@v0.3.4 61 | with: 62 | keep_latest: 30 63 | delete_tags: true 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/rand" 7 | "encoding/base64" 8 | "encoding/binary" 9 | "encoding/json" 10 | "errors" 11 | "flag" 12 | "fmt" 13 | "math/big" 14 | "net" 15 | "net/http" 16 | "net/netip" 17 | "os" 18 | "sort" 19 | "strconv" 20 | "strings" 21 | "sync" 22 | "time" 23 | 24 | "github.com/flynn/noise" 25 | "golang.org/x/crypto/blake2s" 26 | "golang.org/x/crypto/curve25519" 27 | ) 28 | 29 | const ( 30 | asnToFilter = 13335 // The CloudFlare ASN 31 | url = "https://bgp.tools/table.jsonl" // URL for the JSONL table dump 32 | userAgent = "compassvpn-cf-tools bgp.tools" // Custom User-Agent header 33 | 34 | ConcurrentPrefixes = 89 // Number of Concurrencies 35 | RetryCount = 4 // Number of retries if one checker fails 36 | 37 | RetryDelay = 1 * time.Second // Delay between each retry 38 | RequestTimeout = 1 * time.Second // Timeout delay 39 | 40 | TestIPIncrement1 = 13 // First IP to check in a /24 prefix 41 | TestIPIncrement2 = 69 // Second IP to check in a /24 prefix 42 | TestIPIncrement3 = 144 // Third IP to check in a /24 prefix 43 | 44 | defaultInputFile = "all_cf_v4.txt" // Default output file name: All CloudFlare IPv4 ranges converted to /24 prefixes 45 | defaultCDNOutputFile = "all_cdn_v4.txt" // Default output file name: All CloudFlare CDN IPv4 with /24 prefixes 46 | defaultWARPOutputFile = "all_warp_v4.txt" // Default output file name: All CloudFlare WARP IPv4 with /24 prefixes 47 | 48 | // WARP Wireguard configurations 49 | privateKeyB64 = "0ALZyBx68KO4by/oQR+3kmPpYbrOuq605aBYv5GKU0Y=" 50 | publicKeyB64 = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=" 51 | presharedKeyB64 = "" 52 | ) 53 | 54 | var ( 55 | httpClient = &http.Client{Timeout: RequestTimeout} 56 | scanPorts = []int{2408} // List of ports to scan WARP. Example: {2408, 7559, 2371, 894, ...} 57 | ) 58 | 59 | // Prefix structure for JSON parsing 60 | type Prefix struct { 61 | CIDR netip.Prefix `json:"CIDR"` 62 | ASN int `json:"ASN"` 63 | } 64 | 65 | // Holds the result of prefix processing 66 | type PrefixResult struct { 67 | Prefix netip.Prefix 68 | IsValid bool 69 | } 70 | 71 | // Helper function to show usage 72 | func showHelp() { 73 | fmt.Println("Usage:") 74 | fmt.Println(" -h, --help Show help") 75 | fmt.Println(" -f, --fetch Fetch and convert to /24 only") 76 | fmt.Println(" -c, --cdn Run the CDN checker") 77 | fmt.Println(" -w, --warp Run the WARP checker") 78 | fmt.Println(" -o, --output Specify the output file name") 79 | } 80 | 81 | // Fetches the prefixes from the URL and filters them by the given ASN 82 | func fetchAndFilterPrefixes(url string, asn int) ([]netip.Prefix, error) { 83 | req, err := http.NewRequest("GET", url, nil) 84 | if err != nil { 85 | return nil, fmt.Errorf("creating request: %w", err) 86 | } 87 | 88 | // Set the custom User-Agent header 89 | req.Header.Set("User-Agent", userAgent) 90 | 91 | client := &http.Client{} 92 | resp, err := client.Do(req) 93 | if err != nil { 94 | return nil, fmt.Errorf("making request: %w", err) 95 | } 96 | defer resp.Body.Close() 97 | 98 | // Check for non-200 status codes 99 | if resp.StatusCode != http.StatusOK { 100 | return nil, fmt.Errorf("received non-200 status code %d", resp.StatusCode) 101 | } 102 | 103 | var v4Prefixes []netip.Prefix 104 | scanner := bufio.NewScanner(resp.Body) 105 | for scanner.Scan() { 106 | var prefix Prefix 107 | line := scanner.Text() 108 | 109 | // Decode each JSON object 110 | if err := json.Unmarshal([]byte(line), &prefix); err != nil { 111 | fmt.Printf("Error decoding JSON line: %v\n", err) 112 | continue 113 | } 114 | 115 | // Filter for the specified ASN and store IPv4 prefixes 116 | if prefix.ASN == asn && !prefix.CIDR.Addr().Is6() { 117 | v4Prefixes = append(v4Prefixes, prefix.CIDR) 118 | } 119 | } 120 | 121 | // Check for errors during scanning 122 | if err := scanner.Err(); err != nil { 123 | return nil, fmt.Errorf("scanning response: %w", err) 124 | } 125 | 126 | return v4Prefixes, nil 127 | } 128 | 129 | // Converts the prefixes to /24 blocks and writes them to the output file 130 | func convertTo24AndWrite(prefixes []netip.Prefix, outputFile string) error { 131 | outFile, err := os.Create(outputFile) 132 | if err != nil { 133 | return fmt.Errorf("creating output file: %w", err) 134 | } 135 | defer outFile.Close() 136 | 137 | writer := bufio.NewWriter(outFile) 138 | defer writer.Flush() 139 | 140 | unique24s := make(map[string]struct{}) 141 | var wg sync.WaitGroup 142 | prefixChan := make(chan string, len(prefixes)) 143 | 144 | // Process each prefix in a separate goroutine 145 | for _, prefix := range prefixes { 146 | wg.Add(1) 147 | go func(p netip.Prefix) { 148 | defer wg.Done() 149 | processPrefix(p, prefixChan) 150 | }(prefix) 151 | } 152 | 153 | // Close the channel once all processing is done 154 | go func() { 155 | wg.Wait() 156 | close(prefixChan) 157 | }() 158 | 159 | // Collect processed prefixes 160 | var sortedPrefixes []string 161 | for line := range prefixChan { 162 | if _, exists := unique24s[line]; !exists { 163 | unique24s[line] = struct{}{} 164 | sortedPrefixes = append(sortedPrefixes, line) 165 | } 166 | } 167 | 168 | // Sort prefixes 169 | sort.Slice(sortedPrefixes, func(i, j int) bool { 170 | return compareIPs(sortedPrefixes[i], sortedPrefixes[j]) 171 | }) 172 | 173 | for _, prefix := range sortedPrefixes { 174 | if _, err := writer.WriteString(prefix + "\n"); err != nil { 175 | return fmt.Errorf("writing to output file: %w", err) 176 | } 177 | } 178 | 179 | return nil 180 | } 181 | 182 | // Compares two IP addresses based on the first three octets 183 | func compareIPs(ip1, ip2 string) bool { 184 | parts1 := strings.Split(ip1, ".") 185 | parts2 := strings.Split(ip2, ".") 186 | 187 | for i := 0; i < 3; i++ { 188 | num1, _ := strconv.Atoi(parts1[i]) 189 | num2, _ := strconv.Atoi(parts2[i]) 190 | if num1 != num2 { 191 | return num1 < num2 192 | } 193 | } 194 | 195 | return false 196 | } 197 | 198 | // Processes a single prefix and sends /24 blocks to the channel 199 | func processPrefix(prefix netip.Prefix, prefixChan chan<- string) { 200 | ip := prefix.Addr() 201 | prefixLen := prefix.Bits() 202 | 203 | // Directly send /24 prefixes 204 | if prefixLen == 24 { 205 | prefixChan <- prefix.String() 206 | return 207 | } 208 | 209 | ipInt := ipToInt(ip) 210 | numBlocks := 1 << (24 - prefixLen) 211 | for i := 0; i < numBlocks; i++ { 212 | ip := intToIP(ipInt) 213 | net24 := netip.PrefixFrom(ip, 24).String() 214 | prefixChan <- net24 215 | incrementIPBy24(ipInt) 216 | } 217 | } 218 | 219 | // Converts an IP address to a big integer 220 | func ipToInt(ip netip.Addr) *big.Int { 221 | ipInt := big.NewInt(0) 222 | ipInt.SetBytes(ip.AsSlice()) 223 | return ipInt 224 | } 225 | 226 | // Increments an IP address by one /24 block using bit shift operations 227 | func incrementIPBy24(ipInt *big.Int) { 228 | increment := big.NewInt(1 << 8) // 256 for /24 229 | ipInt.Add(ipInt, increment) 230 | } 231 | 232 | // Converts a big integer back to an IP address 233 | func intToIP(ipInt *big.Int) netip.Addr { 234 | ipBytes := make([]byte, 4) 235 | ipInt.FillBytes(ipBytes) 236 | ip, _ := netip.AddrFromSlice(ipBytes) 237 | return ip 238 | } 239 | 240 | // Checks if an IP address responds with StatusOK for the CDN trace URL. 241 | func isValidCDNIP(ip netip.Addr) bool { 242 | url := fmt.Sprintf("http://%s/cdn-cgi/trace", ip) 243 | 244 | for i := 0; i < RetryCount; i++ { 245 | resp, err := httpClient.Head(url) 246 | if err == nil { 247 | defer resp.Body.Close() 248 | if resp.StatusCode == http.StatusOK { 249 | return true 250 | } 251 | } 252 | time.Sleep(RetryDelay) 253 | } 254 | return false 255 | } 256 | 257 | // Increments the given IP address by a specified value. 258 | func incrementIP(ip netip.Addr, increment int) netip.Addr { 259 | ipBytes := ip.As4() 260 | ipUint := uint32(ipBytes[0])<<24 | uint32(ipBytes[1])<<16 | uint32(ipBytes[2])<<8 | uint32(ipBytes[3]) 261 | ipUint += uint32(increment) 262 | newIP := netip.AddrFrom4([4]byte{byte(ipUint >> 24), byte(ipUint >> 16), byte(ipUint >> 8), byte(ipUint)}) 263 | return newIP 264 | } 265 | 266 | // Checks if any IP within the prefix is a CDN IP. 267 | func processPrefixCDN(prefix netip.Prefix) bool { 268 | baseIP := prefix.Addr() 269 | testIP1 := incrementIP(baseIP, TestIPIncrement1) 270 | testIP2 := incrementIP(baseIP, TestIPIncrement2) 271 | testIP3 := incrementIP(baseIP, TestIPIncrement3) 272 | return isValidCDNIP(testIP1) || isValidCDNIP(testIP2) || isValidCDNIP(testIP3) 273 | } 274 | 275 | // Processes each prefix and sends the result to the channel. 276 | func parallelFunctionCDN(ipChannel chan PrefixResult, prefix netip.Prefix) { 277 | isValid := processPrefixCDN(prefix) 278 | ipChannel <- PrefixResult{ 279 | Prefix: prefix, 280 | IsValid: isValid, 281 | } 282 | } 283 | 284 | // Generates a static keypair from a base64-encoded private key 285 | func staticKeypair(privateKeyBase64 string) (noise.DHKey, error) { 286 | privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64) 287 | if err != nil { 288 | return noise.DHKey{}, err 289 | } 290 | 291 | var pubkey, privkey [32]byte 292 | copy(privkey[:], privateKey) 293 | curve25519.ScalarBaseMult(&pubkey, &privkey) 294 | 295 | return noise.DHKey{ 296 | Private: privateKey, 297 | Public: pubkey[:], 298 | }, nil 299 | } 300 | 301 | // Generates an ephemeral keypair 302 | func ephemeralKeypair() (noise.DHKey, error) { 303 | ephemeralPrivateKey := make([]byte, 32) 304 | if _, err := rand.Read(ephemeralPrivateKey); err != nil { 305 | return noise.DHKey{}, err 306 | } 307 | 308 | ephemeralPublicKey, err := curve25519.X25519(ephemeralPrivateKey, curve25519.Basepoint) 309 | if err != nil { 310 | return noise.DHKey{}, err 311 | } 312 | 313 | return noise.DHKey{ 314 | Private: ephemeralPrivateKey, 315 | Public: ephemeralPublicKey, 316 | }, nil 317 | } 318 | 319 | // Converts a uint32 to a byte slice 320 | func uint32ToBytes(n uint32) []byte { 321 | b := make([]byte, 4) 322 | binary.LittleEndian.PutUint32(b, n) 323 | return b 324 | } 325 | 326 | // Initiates a handshake with the server and returns the round-trip time (RTT) 327 | func initiateHandshake(serverAddr netip.AddrPort, privateKeyBase64, peerPublicKeyBase64, presharedKeyBase64 string) (time.Duration, error) { 328 | staticKeyPair, err := staticKeypair(privateKeyBase64) 329 | if err != nil { 330 | return 0, err 331 | } 332 | 333 | peerPublicKey, err := base64.StdEncoding.DecodeString(peerPublicKeyBase64) 334 | if err != nil { 335 | return 0, err 336 | } 337 | 338 | presharedKey, err := base64.StdEncoding.DecodeString(presharedKeyBase64) 339 | if err != nil { 340 | return 0, err 341 | } 342 | 343 | if presharedKeyBase64 == "" { 344 | presharedKey = make([]byte, 32) 345 | } 346 | 347 | ephemeral, err := ephemeralKeypair() 348 | if err != nil { 349 | return 0, err 350 | } 351 | 352 | cs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2s) 353 | hs, err := noise.NewHandshakeState(noise.Config{ 354 | CipherSuite: cs, 355 | Pattern: noise.HandshakeIK, 356 | Initiator: true, 357 | StaticKeypair: staticKeyPair, 358 | PeerStatic: peerPublicKey, 359 | Prologue: []byte("WireGuard v1 zx2c4 Jason@zx2c4.com"), 360 | PresharedKey: presharedKey, 361 | PresharedKeyPlacement: 2, 362 | EphemeralKeypair: ephemeral, 363 | Random: rand.Reader, 364 | }) 365 | if err != nil { 366 | return 0, err 367 | } 368 | 369 | now := time.Now().UTC() 370 | epochOffset := int64(4611686018427387914) // TAI offset from Unix epoch 371 | 372 | tai64nTimestampBuf := make([]byte, 0, 16) 373 | tai64nTimestampBuf = binary.BigEndian.AppendUint64(tai64nTimestampBuf, uint64(epochOffset+now.Unix())) 374 | tai64nTimestampBuf = binary.BigEndian.AppendUint32(tai64nTimestampBuf, uint32(now.Nanosecond())) 375 | msg, _, _, err := hs.WriteMessage(nil, tai64nTimestampBuf) 376 | if err != nil { 377 | return 0, err 378 | } 379 | 380 | initiationPacket := new(bytes.Buffer) 381 | binary.Write(initiationPacket, binary.BigEndian, []byte{0x01, 0x00, 0x00, 0x00}) 382 | binary.Write(initiationPacket, binary.BigEndian, uint32ToBytes(28)) 383 | binary.Write(initiationPacket, binary.BigEndian, msg) 384 | 385 | macKey := blake2s.Sum256(append([]byte("mac1----"), peerPublicKey...)) 386 | hasher, err := blake2s.New128(macKey[:]) 387 | if err != nil { 388 | return 0, err 389 | } 390 | _, err = hasher.Write(initiationPacket.Bytes()) 391 | if err != nil { 392 | return 0, err 393 | } 394 | initiationPacketMAC := hasher.Sum(nil) 395 | 396 | binary.Write(initiationPacket, binary.BigEndian, initiationPacketMAC[:16]) 397 | binary.Write(initiationPacket, binary.BigEndian, [16]byte{}) 398 | 399 | conn, err := net.Dial("udp", serverAddr.String()) 400 | if err != nil { 401 | return 0, err 402 | } 403 | defer conn.Close() 404 | 405 | _, err = initiationPacket.WriteTo(conn) 406 | if err != nil { 407 | return 0, err 408 | } 409 | t0 := time.Now() 410 | 411 | response := make([]byte, 92) 412 | conn.SetReadDeadline(time.Now().Add(5 * time.Second)) 413 | i, err := conn.Read(response) 414 | if err != nil { 415 | return 0, err 416 | } 417 | rtt := time.Since(t0) 418 | 419 | if i < 60 { 420 | return 0, fmt.Errorf("invalid handshake response length %d bytes", i) 421 | } 422 | 423 | if response[0] != 2 { 424 | return 0, errors.New("invalid response type") 425 | } 426 | 427 | ourIndex := binary.LittleEndian.Uint32(response[8:12]) 428 | if ourIndex != 28 { 429 | return 0, errors.New("invalid sender index in response") 430 | } 431 | 432 | payload, _, _, err := hs.ReadMessage(nil, response[12:60]) 433 | if err != nil { 434 | return 0, err 435 | } 436 | 437 | if len(payload) != 0 { 438 | return 0, errors.New("unexpected payload in response") 439 | } 440 | 441 | return rtt, nil 442 | } 443 | 444 | // Checks if an IP address responds with StatusOK for the WARP trace URL on any of the given ports 445 | func isValidWarpIP(ip netip.Addr) bool { 446 | for _, port := range scanPorts { 447 | ip2 := netip.AddrPortFrom(ip, uint16(port)) 448 | 449 | for i := 0; i < RetryCount; i++ { 450 | _, err := initiateHandshake(ip2, privateKeyB64, publicKeyB64, presharedKeyB64) 451 | if err == nil { 452 | return true 453 | } 454 | time.Sleep(RetryDelay) 455 | } 456 | } 457 | return false 458 | } 459 | 460 | // Checks if any IP within the prefix is a WARP IP 461 | func processPrefixWARP(prefix netip.Prefix) bool { 462 | baseIP := prefix.Addr() 463 | testIP1 := incrementIP(baseIP, TestIPIncrement1) 464 | testIP2 := incrementIP(baseIP, TestIPIncrement2) 465 | testIP3 := incrementIP(baseIP, TestIPIncrement3) 466 | return isValidWarpIP(testIP1) || isValidWarpIP(testIP2) || isValidWarpIP(testIP3) 467 | } 468 | 469 | // Processes each prefix and sends the result to the channel 470 | func parallelFunctionWARP(ipChannel chan PrefixResult, prefix netip.Prefix) { 471 | isValid := processPrefixWARP(prefix) 472 | ipChannel <- PrefixResult{ 473 | Prefix: prefix, 474 | IsValid: isValid, 475 | } 476 | } 477 | 478 | func main() { 479 | // Define flags 480 | helpFlag := flag.Bool("h", false, "Show help") 481 | helpFlagLong := flag.Bool("help", false, "Show help") 482 | cdnFlag := flag.Bool("c", false, "Run the CDN checker") 483 | cdnFlagLong := flag.Bool("cdn", false, "Run the CDN checker") 484 | warpFlag := flag.Bool("w", false, "Run the WARP checker") 485 | warpFlagLong := flag.Bool("warp", false, "Run the WARP checker") 486 | fetchFlag := flag.Bool("f", false, "Fetch and convert to /24 only") 487 | fetchFlagLong := flag.Bool("fetch", false, "Fetch and convert to /24 only") 488 | outputFile := flag.String("o", "", "Specify the output file name") 489 | outputFileLong := flag.String("output", "", "Specify the output file name") 490 | 491 | flag.Parse() 492 | 493 | // If no flag is provided, show help 494 | if !*helpFlag && !*helpFlagLong && !*cdnFlag && !*cdnFlagLong && !*warpFlag && !*warpFlagLong && !*fetchFlag && !*fetchFlagLong { 495 | showHelp() 496 | return 497 | } 498 | 499 | if *helpFlag || *helpFlagLong { 500 | showHelp() 501 | return 502 | } 503 | 504 | // Fetch and filter the prefixes 505 | v4Prefixes, err := fetchAndFilterPrefixes(url, asnToFilter) 506 | if err != nil { 507 | fmt.Printf("Error fetching and filtering prefixes: %v\n", err) 508 | return 509 | } 510 | 511 | // Determine the output file 512 | output := defaultInputFile 513 | if *outputFile != "" { 514 | output = *outputFile 515 | } 516 | if *outputFileLong != "" { 517 | output = *outputFileLong 518 | } 519 | 520 | // Convert the prefixes to /24 and write to the intermediate file 521 | err = convertTo24AndWrite(v4Prefixes, output) 522 | if err != nil { 523 | fmt.Printf("Error converting to /24 and writing to file: %v\n", err) 524 | return 525 | } 526 | 527 | // If fetch-only flag is set, exit after converting and writing the prefixes 528 | if *fetchFlag || *fetchFlagLong { 529 | fmt.Println("Prefixes fetched and converted to /24. Output written to", output) 530 | return 531 | } 532 | 533 | // Open intermediate file containing IP prefixes 534 | file, err := os.Open(output) 535 | if err != nil { 536 | fmt.Printf("Error opening file: %v\n", err) 537 | return 538 | } 539 | defer file.Close() 540 | 541 | // Read all prefixes and store them in a map to avoid duplicates 542 | prefixSet := make(map[string]struct{}) 543 | scanner := bufio.NewScanner(file) 544 | for scanner.Scan() { 545 | prefixString := scanner.Text() 546 | prefixSet[prefixString] = struct{}{} 547 | } 548 | 549 | // Convert the map keys back to a slice 550 | var prefixes []netip.Prefix 551 | for prefixString := range prefixSet { 552 | prefix, err := netip.ParsePrefix(prefixString) 553 | if err == nil { 554 | prefixes = append(prefixes, prefix) 555 | } 556 | } 557 | 558 | if *cdnFlag || *cdnFlagLong { 559 | output := defaultCDNOutputFile 560 | if *outputFile != "" { 561 | output = *outputFile 562 | } 563 | if *outputFileLong != "" { 564 | output = *outputFileLong 565 | } 566 | 567 | // Create output file for valid CDN IP prefixes 568 | outputFile, err := os.Create(output) 569 | if err != nil { 570 | fmt.Printf("Error creating output file: %v\n", err) 571 | return 572 | } 573 | defer outputFile.Close() 574 | 575 | ipChannel := make(chan PrefixResult) 576 | sem := make(chan struct{}, ConcurrentPrefixes) 577 | 578 | // Process prefixes concurrently with limited concurrency 579 | go func() { 580 | for _, prefix := range prefixes { 581 | sem <- struct{}{} 582 | go func(prefix netip.Prefix) { 583 | defer func() { <-sem }() 584 | parallelFunctionCDN(ipChannel, prefix) 585 | }(prefix) 586 | } 587 | }() 588 | 589 | var validPrefixes []string 590 | // Collect results and store valid prefixes 591 | for i := 0; i < len(prefixes); i++ { 592 | result := <-ipChannel 593 | if result.IsValid { 594 | fmt.Printf("Valid CDN Prefix: %v\n", result.Prefix) 595 | validPrefixes = append(validPrefixes, result.Prefix.String()) 596 | } 597 | } 598 | close(ipChannel) 599 | 600 | // Sort and write valid prefixes to the output file 601 | sort.Slice(validPrefixes, func(i, j int) bool { 602 | return compareIPs(validPrefixes[i], validPrefixes[j]) 603 | }) 604 | for _, prefix := range validPrefixes { 605 | outputFile.WriteString(prefix + "\n") 606 | } 607 | 608 | fmt.Println("Processing complete. Valid /24 prefixes have been written to", output) 609 | } 610 | 611 | if *warpFlag || *warpFlagLong { 612 | output := defaultWARPOutputFile 613 | if *outputFile != "" { 614 | output = *outputFile 615 | } 616 | if *outputFileLong != "" { 617 | output = *outputFileLong 618 | } 619 | 620 | // Create output file for valid WARP IP prefixes 621 | outputFile, err := os.Create(output) 622 | if err != nil { 623 | fmt.Printf("Error creating output file: %v\n", err) 624 | return 625 | } 626 | defer outputFile.Close() 627 | 628 | ipChannel := make(chan PrefixResult) 629 | sem := make(chan struct{}, ConcurrentPrefixes) 630 | 631 | // Process prefixes concurrently with limited concurrency 632 | go func() { 633 | for _, prefix := range prefixes { 634 | sem <- struct{}{} 635 | go func(prefix netip.Prefix) { 636 | defer func() { <-sem }() 637 | parallelFunctionWARP(ipChannel, prefix) 638 | }(prefix) 639 | } 640 | }() 641 | 642 | var validPrefixes []string 643 | // Collect results and store valid prefixes 644 | for i := 0; i < len(prefixes); i++ { 645 | result := <-ipChannel 646 | if result.IsValid { 647 | fmt.Printf("Valid WARP Prefix: %v\n", result.Prefix) 648 | validPrefixes = append(validPrefixes, result.Prefix.String()) 649 | } 650 | } 651 | close(ipChannel) 652 | 653 | // Sort and write valid prefixes to the output file 654 | sort.Slice(validPrefixes, func(i, j int) bool { 655 | return compareIPs(validPrefixes[i], validPrefixes[j]) 656 | }) 657 | for _, prefix := range validPrefixes { 658 | outputFile.WriteString(prefix + "\n") 659 | } 660 | 661 | fmt.Println("Processing complete. Valid /24 prefixes have been written to", output) 662 | } 663 | } 664 | --------------------------------------------------------------------------------