├── LICENSE ├── README.md └── cfping.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MTimer 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cfping.sh 2 | 3 | > ping [fping] all cloudflare ips to find the best ip 4 | 5 | ## Usage 6 | 7 | Invoking `cfping.sh -h` prints usage information: 8 | 9 | $ cfping.sh -h 10 | $ usage: 11 | $ cfping.sh -c [-p 2000] [-s 5] [-g 1] [-m 500] 12 | $ -c ping [fping] all cloudflare ips to find the best ip 13 | $ -p sets the time in milliseconds that fping waits between successive 14 | $ packets to an individual target (default is 2000, minimum is 10) 15 | $ -s set the number of request packets to send to each target (default is 5) 16 | $ -g the minimum amount of time (in milliseconds) between sending a 17 | $ ping packet to any target (default is 1, minimum is 1) 18 | $ -l show cloudflare ip location 19 | $ -m you may want to use this according to system resources limit 20 | larger number faster result (default is 500) 21 | 22 | --- 23 | 24 | $ cfping.sh -d [-L https://domain.com/xxx] [-N 100] [-P 10] [-I ip] 25 | $ -d speed test (default testing best 100 IPs unless -I used) 26 | $ -L set the file link to test (default: https://speed.cloudflare.com/__down?bytes=100001000) 27 | $ the domain of this link must have cname record on cloudflare 28 | $ -N set the number of IPs to test (default is 100) 29 | $ -P set the parallel number of speed test (default is 10) 30 | $ -I specify an ip to test 31 | 32 | --- 33 | 34 | $ cfping.sh [options] 35 | $ -n set the number of addresses to print ( must not be set) 36 | $ -f set the format of addresses (hex, dec, or dot) 37 | $ -i set the increment to 'x' 38 | $ -h display this help message and exit 39 | $ -v display the version number and exit 40 | 41 | ## ping cloudflare 所有的 IP 找到最优 IP 并测速 42 | 43 | ### 使用方法 44 | 45 | `cfping.sh -c` 46 | 47 | - 1分钟就可以得到结果 48 | - 所有的 ip 都在 ip_sorted 49 | - 如果要显示 ip 位置, 加参数 `-l` 50 | 51 | `cfping.sh -d` 52 | 53 | - 默认测速 100 个 ip 54 | - 如果要自定义测速文件, 加参数 `-L` 55 | 文件链接的域名必须在 cloudflare 有 cname 记录 56 | - 如果测速单个指定 IP, 加参数 `-I` 57 | 58 | 部分网络无法分配到香港 ip, 建议更换其他网络 59 | 60 | mac / linux 测试通过 61 | 62 | --- 63 | 64 | ## Credits 65 | 66 | [fping](https://github.com/schweikert/fping) 67 | 68 | [prips.sh](https://github.com/honzahommer/prips.sh) 69 | 70 | [Inquirer.sh](https://github.com/tanhauhau/Inquirer.sh) 71 | 72 | © 2020 MTimer. MIT license. 73 | -------------------------------------------------------------------------------- /cfping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # based on https://raw.githubusercontent.com/tanhauhau/Inquirer.sh/master/dist/inquirer.sh 6 | inquirer() 7 | { 8 | local arrow checked unchecked red green blue cyan bold normal dim 9 | arrow=$(echo -e '\xe2\x9d\xaf') 10 | checked=$(echo -e '\xe2\x97\x89') 11 | unchecked=$(echo -e '\xe2\x97\xaf') 12 | red=$(tput setaf 1) 13 | green=$(tput setaf 2) 14 | blue=$(tput setaf 4) 15 | cyan=$(tput setaf 6) 16 | bold=$(tput bold) 17 | normal=$(tput sgr0) 18 | dim=$'\e[2m' 19 | 20 | inquirer:print() { 21 | echo "$1" 22 | tput el 23 | } 24 | 25 | inquirer:join() { 26 | local IFS=$'\n' 27 | local var=("$1"[@]) 28 | local _join_list=("${!var}") 29 | local first=true 30 | for item in "${_join_list[@]}" 31 | do 32 | if [ "$first" = true ] 33 | then 34 | printf "%s" "$item" 35 | first=false 36 | else 37 | printf "${2-, }%s" "$item" 38 | fi 39 | done 40 | } 41 | 42 | inquirer:gen_env_from_options() { 43 | local IFS=$'\n' 44 | local var=("$1"[@]) 45 | local _indices=("${!var}") 46 | var=("$2"[@]) 47 | local _env_names=("${!var}") 48 | local _checkbox_selected 49 | 50 | for i in $(inquirer:gen_index ${#_env_names[@]}) 51 | do 52 | _checkbox_selected[i]=false 53 | done 54 | 55 | for i in "${_indices[@]}" 56 | do 57 | _checkbox_selected[i]=true 58 | done 59 | 60 | for i in $(inquirer:gen_index ${#_env_names[@]}) 61 | do 62 | printf "%s=%s\n" "${_env_names[i]}" "${_checkbox_selected[i]}" 63 | done 64 | } 65 | 66 | inquirer:on_default() { 67 | true; 68 | } 69 | 70 | inquirer:on_keypress() { 71 | local OLD_IFS=$IFS 72 | local key 73 | local on_up=${1:-inquirer:on_default} 74 | local on_down=${2:-inquirer:on_default} 75 | local on_space=${3:-inquirer:on_default} 76 | local on_enter=${4:-inquirer:on_default} 77 | local on_left=${5:-inquirer:on_default} 78 | local on_right=${6:-inquirer:on_default} 79 | local on_ascii=${7:-inquirer:on_default} 80 | local on_backspace=${8:-inquirer:on_default} 81 | local on_not_ascii=${9:-inquirer:on_default} 82 | _break_keypress=false 83 | while IFS="" read -rsn1 key 84 | do 85 | case "$key" in 86 | $'\x1b') 87 | read -rsn1 key 88 | if [[ "$key" == "[" ]] 89 | then 90 | read -rsn1 key 91 | case "$key" in 92 | 'A') $on_up;; 93 | 'B') $on_down;; 94 | 'D') $on_left;; 95 | 'C') $on_right;; 96 | esac 97 | fi 98 | ;; 99 | $'\x20') $on_space;; 100 | $'\x7f') $on_backspace $key;; 101 | '') $on_enter $key;; 102 | *[$'\x80'-$'\xFF']*) $on_not_ascii $key;; 103 | # [^ -~] 104 | *) $on_ascii $key;; 105 | esac 106 | if [ "$_break_keypress" = true ] 107 | then 108 | break 109 | fi 110 | done 111 | IFS=$OLD_IFS 112 | } 113 | 114 | inquirer:gen_index() { 115 | local k=$1 116 | local l=0 117 | for((l=0;l 选择, 确认)${normal}" 308 | 309 | for i in $(inquirer:gen_index ${#_checkbox_list[@]}) 310 | do 311 | _checkbox_selected[i]=false 312 | done 313 | 314 | if [ -n "${3:-}" ] 315 | then 316 | var=("$3"[@]) 317 | _selected_indices=("${!var}") 318 | for i in "${_selected_indices[@]}" 319 | do 320 | _checkbox_selected[i]=true 321 | done 322 | fi 323 | 324 | for i in $(inquirer:gen_index ${#_checkbox_list[@]}) 325 | do 326 | tput cub "$(tput cols)" 327 | if [ $i = 0 ] 328 | then 329 | if [ "${_checkbox_selected[i]}" = true ] 330 | then 331 | inquirer:print "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[i]} ${normal}" 332 | else 333 | inquirer:print "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[i]} ${normal}" 334 | fi 335 | else 336 | if [ "${_checkbox_selected[i]}" = true ] 337 | then 338 | inquirer:print " ${green}${checked}${normal} ${_checkbox_list[i]} ${normal}" 339 | else 340 | inquirer:print " ${unchecked} ${_checkbox_list[i]} ${normal}" 341 | fi 342 | fi 343 | tput el 344 | done 345 | 346 | for j in $(inquirer:gen_index ${#_checkbox_list[@]}) 347 | do 348 | tput cuu1 349 | done 350 | 351 | inquirer:on_keypress inquirer:on_checkbox_input_up inquirer:on_checkbox_input_down inquirer:on_checkbox_input_space inquirer:on_checkbox_input_enter inquirer:on_default inquirer:on_default inquirer:on_checkbox_input_ascii 352 | } 353 | 354 | inquirer:checkbox_input() { 355 | inquirer:_checkbox_input "$1" "$2" 356 | _checkbox_input_output_var_name=$3 357 | inquirer:select_indices _checkbox_list _checkbox_selected_indices $_checkbox_input_output_var_name 358 | 359 | unset _checkbox_list 360 | unset _break_keypress 361 | unset _first_keystroke 362 | unset _current_index 363 | unset _checkbox_input_output_var_name 364 | unset _checkbox_selected_indices 365 | unset _checkbox_selected_options 366 | 367 | inquirer:cleanup 368 | } 369 | 370 | inquirer:checkbox_input_indices() { 371 | inquirer:_checkbox_input "$1" "$2" "$3" 372 | _checkbox_input_output_var_name=$3 373 | 374 | declare -a new_array 375 | for i in $(inquirer:gen_index ${#_checkbox_selected_indices[@]}) 376 | do 377 | new_array+=("${_checkbox_selected_indices[i]}") 378 | done 379 | read -r -a ${_checkbox_input_output_var_name?} <<< "${new_array[@]}" 380 | unset new_array 381 | 382 | unset _checkbox_list 383 | unset _break_keypress 384 | unset _first_keystroke 385 | unset _current_index 386 | unset _checkbox_input_output_var_name 387 | unset _checkbox_selected_indices 388 | unset _checkbox_selected_options 389 | 390 | inquirer:cleanup 391 | } 392 | 393 | inquirer:on_list_input_up() { 394 | inquirer:remove_list_instructions 395 | tput cub "$(tput cols)" 396 | 397 | printf '%s' " ${_list_options[$_list_selected_index]}" 398 | tput el 399 | 400 | if [ $_list_selected_index = 0 ] 401 | then 402 | _list_selected_index=$((${#_list_options[@]}-1)) 403 | tput cud $((${#_list_options[@]}-1)) 404 | tput cub "$(tput cols)" 405 | else 406 | _list_selected_index=$((_list_selected_index-1)) 407 | 408 | tput cuu1 409 | tput cub "$(tput cols)" 410 | tput el 411 | fi 412 | 413 | printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" 414 | } 415 | 416 | inquirer:on_list_input_down() { 417 | inquirer:remove_list_instructions 418 | tput cub "$(tput cols)" 419 | 420 | printf '%s' " ${_list_options[$_list_selected_index]}" 421 | tput el 422 | 423 | if [ $_list_selected_index = $((${#_list_options[@]}-1)) ] 424 | then 425 | _list_selected_index=0 426 | tput cuu $((${#_list_options[@]}-1)) 427 | tput cub "$(tput cols)" 428 | else 429 | _list_selected_index=$((_list_selected_index+1)) 430 | tput cud1 431 | tput cub "$(tput cols)" 432 | tput el 433 | fi 434 | printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}" 435 | } 436 | 437 | inquirer:on_list_input_enter_space() { 438 | local OLD_IFS=$IFS 439 | IFS=$'\n' 440 | 441 | tput cud $((${#_list_options[@]}-_list_selected_index)) 442 | tput cub "$(tput cols)" 443 | 444 | for i in $(seq $((${#_list_options[@]}+1))) 445 | do 446 | tput el1 447 | tput el 448 | tput cuu1 449 | done 450 | tput cub "$(tput cols)" 451 | 452 | tput cuf $((prompt_width+3)) 453 | printf '%s' "${cyan}${_list_options[$_list_selected_index]}${normal}" 454 | tput el 455 | 456 | tput cud1 457 | tput cub "$(tput cols)" 458 | tput el 459 | 460 | _break_keypress=true 461 | IFS=$OLD_IFS 462 | } 463 | 464 | inquirer:on_list_input_input_ascii() { 465 | local key=$1 466 | case $key in 467 | "w" ) inquirer:on_list_input_up;; 468 | "s" ) inquirer:on_list_input_down;; 469 | esac 470 | } 471 | 472 | inquirer:remove_list_instructions() { 473 | if [ "$_first_keystroke" = true ] 474 | then 475 | tput cuu $((_list_selected_index+1)) 476 | tput cub "$(tput cols)" 477 | tput cuf $((prompt_width+3)) 478 | tput el 479 | tput cud $((_list_selected_index+1)) 480 | _first_keystroke=false 481 | fi 482 | } 483 | 484 | inquirer:_list_input() { 485 | local i j var=("$2"[@]) 486 | _list_options=("${!var}") 487 | 488 | _list_selected_index=0 489 | _first_keystroke=true 490 | 491 | trap inquirer:control_c SIGINT EXIT 492 | 493 | stty -echo 494 | tput civis 495 | 496 | inquirer:print "${green}?${normal} ${bold}${prompt}${normal} ${dim}(使用上下箭头选择)${normal}" 497 | 498 | for i in $(inquirer:gen_index ${#_list_options[@]}) 499 | do 500 | tput cub "$(tput cols)" 501 | if [ $i = 0 ] 502 | then 503 | inquirer:print "${cyan}${arrow} ${_list_options[i]} ${normal}" 504 | else 505 | inquirer:print " ${_list_options[i]}" 506 | fi 507 | tput el 508 | done 509 | 510 | for j in $(inquirer:gen_index ${#_list_options[@]}) 511 | do 512 | tput cuu1 513 | done 514 | 515 | inquirer:on_keypress inquirer:on_list_input_up inquirer:on_list_input_down inquirer:on_list_input_enter_space inquirer:on_list_input_enter_space inquirer:on_default inquirer:on_default inquirer:on_list_input_input_ascii 516 | } 517 | 518 | inquirer:list_input() { 519 | inquirer:_list_input "$1" "$2" 520 | var_name=$3 521 | read -r ${var_name?} <<< "${_list_options[$_list_selected_index]}" 522 | unset _list_selected_index 523 | unset _list_options 524 | unset _break_keypress 525 | unset _first_keystroke 526 | 527 | inquirer:cleanup 528 | } 529 | 530 | inquirer:list_input_index() { 531 | inquirer:_list_input "$1" "$2" 532 | var_name=$3 533 | read -r ${var_name?} <<< "$_list_selected_index" 534 | unset _list_selected_index 535 | unset _list_options 536 | unset _break_keypress 537 | unset _first_keystroke 538 | 539 | inquirer:cleanup 540 | } 541 | 542 | inquirer:on_text_input_left() { 543 | inquirer:remove_regex_failed 544 | if [[ $_current_pos -gt 0 ]] 545 | then 546 | local current=${_text_input:$_current_pos:1} current_width 547 | current_width=$(inquirer:display_length "$current") 548 | 549 | tput cub $current_width 550 | _current_pos=$((_current_pos-1)) 551 | fi 552 | } 553 | 554 | inquirer:on_text_input_right() { 555 | inquirer:remove_regex_failed 556 | if [[ $((_current_pos+1)) -eq ${#_text_input} ]] 557 | then 558 | tput cuf1 559 | _current_pos=$((_current_pos+1)) 560 | elif [[ $_current_pos -lt ${#_text_input} ]] 561 | then 562 | local next=${_text_input:$((_current_pos+1)):1} next_width 563 | next_width=$(inquirer:display_length "$next") 564 | 565 | tput cuf $next_width 566 | _current_pos=$((_current_pos+1)) 567 | fi 568 | } 569 | 570 | inquirer:on_text_input_enter() { 571 | inquirer:remove_regex_failed 572 | 573 | _text_input=${_text_input:-$_text_default_value} 574 | 575 | if [[ $($_text_input_validator "$_text_input") = true ]] 576 | then 577 | tput cuu 1 578 | tput cub "$(tput cols)" 579 | tput cuf $((prompt_width+3)) 580 | printf '%s' "${cyan}${_text_input}${normal}" 581 | tput el 582 | tput cud1 583 | tput cub "$(tput cols)" 584 | tput el 585 | read -r ${var_name?} <<< "$_text_input" 586 | _break_keypress=true 587 | else 588 | _text_input_regex_failed=true 589 | tput civis 590 | tput cuu1 591 | tput cub "$(tput cols)" 592 | tput cuf $((prompt_width+3)) 593 | tput el 594 | tput cud1 595 | tput cub "$(tput cols)" 596 | tput el 597 | tput cud1 598 | tput cub "$(tput cols)" 599 | printf '%b' "${red}$_text_input_regex_failed_msg${normal}" 600 | tput el 601 | _text_input="" 602 | _current_pos=0 603 | tput cnorm 604 | fi 605 | } 606 | 607 | inquirer:on_text_input_ascii() { 608 | inquirer:remove_regex_failed 609 | local c=${1:- } 610 | 611 | local rest=${_text_input:$_current_pos} rest_width 612 | local current=${_text_input:$_current_pos:1} current_width 613 | rest_width=$(inquirer:display_length "$rest") 614 | current_width=$(inquirer:display_length "$current") 615 | 616 | _text_input="${_text_input:0:$_current_pos}$c$rest" 617 | _current_pos=$((_current_pos+1)) 618 | 619 | tput civis 620 | [[ $current_width -gt 1 ]] && tput cub $((current_width-1)) 621 | printf '%s' "$c$rest" 622 | tput el 623 | 624 | if [[ $rest_width -gt 0 ]] 625 | then 626 | tput cub $((rest_width-current_width+1)) 627 | fi 628 | tput cnorm 629 | } 630 | 631 | inquirer:display_length() { 632 | local display_length=0 byte_len 633 | local oLC_ALL=${LC_ALL:-} oLANG=${LANG:-} LC_ALL=${LC_ALL:-} LANG=${LANG:-} 634 | 635 | while IFS="" read -rsn1 char 636 | do 637 | case "$char" in 638 | '') 639 | ;; 640 | *[$'\x80'-$'\xFF']*) 641 | LC_ALL='' LANG=C 642 | byte_len=${#char} 643 | LC_ALL=$oLC_ALL LANG=$oLANG 644 | if [[ $byte_len -eq 2 ]] 645 | then 646 | display_length=$((display_length+1)) 647 | else 648 | display_length=$((display_length+2)) 649 | fi 650 | ;; 651 | *) 652 | display_length=$((display_length+1)) 653 | ;; 654 | esac 655 | done <<< "$1" 656 | 657 | echo "$display_length" 658 | } 659 | 660 | inquirer:on_text_input_not_ascii() { 661 | inquirer:remove_regex_failed 662 | local c=$1 663 | 664 | local rest="${_text_input:$_current_pos}" rest_width 665 | local current=${_text_input:$_current_pos:1} current_width 666 | rest_width=$(inquirer:display_length "$rest") 667 | current_width=$(inquirer:display_length "$current") 668 | 669 | _text_input="${_text_input:0:$_current_pos}$c$rest" 670 | _current_pos=$((_current_pos+1)) 671 | 672 | tput civis 673 | [[ $current_width -gt 1 ]] && tput cub $((current_width-1)) 674 | printf '%s' "$c$rest" 675 | tput el 676 | 677 | if [[ $rest_width -gt 0 ]] 678 | then 679 | tput cub $((rest_width-current_width+1)) 680 | fi 681 | tput cnorm 682 | } 683 | 684 | inquirer:on_text_input_backspace() { 685 | inquirer:remove_regex_failed 686 | if [ $_current_pos -gt 0 ] || { [ $_current_pos -eq 0 ] && [ "${#_text_input}" -gt 0 ]; } 687 | then 688 | local start rest rest_width del del_width next next_width offset 689 | local current=${_text_input:$_current_pos:1} current_width 690 | current_width=$(inquirer:display_length "$current") 691 | 692 | tput civis 693 | if [ $_current_pos -eq 0 ] 694 | then 695 | rest=${_text_input:$((_current_pos+1))} 696 | next=${_text_input:$((_current_pos+1)):1} 697 | rest_width=$(inquirer:display_length "$rest") 698 | next_width=$(inquirer:display_length "$next") 699 | offset=$((current_width-1)) 700 | [[ $offset -gt 0 ]] && tput cub $offset 701 | printf '%s' "$rest" 702 | tput el 703 | offset=$((rest_width-next_width+1)) 704 | [[ $offset -gt 0 ]] && tput cub $offset 705 | _text_input=$rest 706 | else 707 | rest=${_text_input:$_current_pos} 708 | start=${_text_input:0:$((_current_pos-1))} 709 | del=${_text_input:$((_current_pos-1)):1} 710 | rest_width=$(inquirer:display_length "$rest") 711 | del_width=$(inquirer:display_length "$del") 712 | _current_pos=$((_current_pos-1)) 713 | if [[ $current_width -gt 1 ]] 714 | then 715 | tput cub $((del_width+current_width-1)) 716 | printf '%s' "$rest" 717 | tput el 718 | tput cub $((rest_width-current_width+1)) 719 | else 720 | tput cub $del_width 721 | printf '%s' "$rest" 722 | tput el 723 | [[ $rest_width -gt 0 ]] && tput cub $((rest_width-current_width+1)) 724 | fi 725 | _text_input="$start$rest" 726 | fi 727 | tput cnorm 728 | fi 729 | } 730 | 731 | inquirer:remove_regex_failed() { 732 | if [ "$_text_input_regex_failed" = true ] 733 | then 734 | _text_input_regex_failed=false 735 | tput sc 736 | tput cud1 737 | tput el1 738 | tput el 739 | tput rc 740 | fi 741 | } 742 | 743 | inquirer:text_input_default_validator() { 744 | echo true; 745 | } 746 | 747 | inquirer:text_input() { 748 | var_name=$2 749 | if [ -n "$_text_default_value" ] 750 | then 751 | _text_default_tip=" $dim($_text_default_value)" 752 | else 753 | _text_default_tip="" 754 | fi 755 | _text_input_regex_failed_msg=${4:-"输入验证错误"} 756 | _text_input_validator=${5:-inquirer:text_input_default_validator} 757 | _text_input_regex_failed=false 758 | 759 | inquirer:print "${green}?${normal} ${bold}${prompt}$_text_default_tip${normal}" 760 | 761 | trap inquirer:control_c SIGINT EXIT 762 | 763 | stty -echo 764 | tput cnorm 765 | 766 | inquirer:on_keypress inquirer:on_default inquirer:on_default inquirer:on_text_input_ascii inquirer:on_text_input_enter inquirer:on_text_input_left inquirer:on_text_input_right inquirer:on_text_input_ascii inquirer:on_text_input_backspace inquirer:on_text_input_not_ascii 767 | read -r ${var_name?} <<< "$_text_input" 768 | 769 | inquirer:cleanup 770 | } 771 | 772 | local option=$1 773 | shift 774 | local var_name prompt=${1:-} prompt_width _text_default_value=${3:-} _current_pos=0 _text_input="" _text_input_regex_failed_msg _text_input_validator _text_input_regex_failed 775 | prompt_width=$(inquirer:display_length "$prompt") 776 | inquirer:$option "$@" 777 | } 778 | 779 | # based on https://raw.githubusercontent.com/kahkhang/ora.sh/master/ora.sh 780 | spinner(){ 781 | local i=1 delay=0.05 FUNCTION_NAME="$2" VARIABLE_NAME="${3:-}" list tempfile 782 | local green cyan normal 783 | green=$(tput setaf 2) 784 | cyan=$(tput setaf 6) 785 | normal=$(tput sgr0) 786 | 787 | IFS=" " read -a list < <(echo -e '\xe2\xa0\x8b \xe2\xa0\x99 \xe2\xa0\xb9 \xe2\xa0\xb8 \xe2\xa0\xbc \xe2\xa0\xb4 \xe2\xa0\xa6 \xe2\xa0\xa7 \xe2\xa0\x87 \xe2\xa0\x8f') 788 | tempfile=$(mktemp) 789 | 790 | trap 'inquirer cleanup' SIGINT 791 | 792 | stty -echo && tput civis 793 | $FUNCTION_NAME >> "$tempfile" 2>>"$tempfile" & 794 | local pid=$! 795 | 796 | echo 797 | tput sc 798 | printf "%s %s" "${list[i]}" "$green$1${normal}" 799 | tput el 800 | tput rc 801 | 802 | while ps -p $pid -o pid= >/dev/null 803 | do 804 | printf "%s" "$cyan${list[i]}${normal}" 805 | i=$(((i+1)%10)) 806 | sleep $delay 807 | printf "\b\b\b" 808 | done 809 | tput el 810 | 811 | if [[ -n $VARIABLE_NAME ]] 812 | then 813 | read -r ${VARIABLE_NAME?} < "$tempfile" 814 | else 815 | awk '{print}' "$tempfile" 816 | fi 817 | 818 | rm -f "$tempfile" 819 | 820 | tput cnorm && stty echo 821 | 822 | trap - SIGINT 823 | 824 | wait $pid 825 | } 826 | 827 | cfping() { 828 | local green red normal var 829 | green=$(tput setaf 2) 830 | red=$(tput setaf 1) 831 | normal=$(tput sgr0) 832 | 833 | cfping:help() { 834 | cat << EOF 1>&2 835 | usage: 836 | $_this -c [-l] [-s 2000] [-p 5] [-g 1] [-m 500] 837 | -c ping [fping] all cloudflare ips to find the best ip 838 | -s set the time in milliseconds that fping waits between successive 839 | packets to an individual target (default is 2000, minimum is 10) 840 | -p set the number of request packets to send to each target (default is 5) 841 | -g the minimum amount of time (in milliseconds) between sending a 842 | ping packet to any target (default is 1, minimum is 1) 843 | -l show cloudflare ip location 844 | -m you may want to use this according to system resources limit 845 | larger number faster result (default is 500) 846 | 847 | --- 848 | 849 | $_this -d [-L https://domain.com/xxx] [-N 100] [-P 10] [-I ip] 850 | -d speed test (default testing best 100 IPs unless -I used) 851 | -L set the file link to test (default: https://speed.cloudflare.com/__down?bytes=100001000) 852 | the domain of this link must have cname record on cloudflare 853 | -N set the number of IPs to test (default is 100) 854 | -P set the parallel number of speed test (default is 10) 855 | -I specify an ip to test 856 | 857 | --- 858 | 859 | $_this [options] 860 | -n set the number of addresses to print ( must not be set) 861 | -f set the format of addresses (hex, dec, or dot) 862 | -i set the increment to 'x' 863 | -h display this help message and exit 864 | -v display the version number and exit 865 | 866 | EOF 867 | exit 1 868 | } 869 | 870 | cfping:info() { 871 | printf '%b' "\n $*\n" 2>&1 872 | } 873 | 874 | cfping:error() { 875 | local code=1 876 | 877 | case ${1} in 878 | -[0-9]*) 879 | code=${1#-} 880 | shift 881 | ;; 882 | esac 883 | 884 | printf '%b' "\n $_this: $red$*$normal\n\n" 1>&2 885 | exit "$code" 886 | } 887 | 888 | cfping:aton() { 889 | local ip=$1 890 | local ipnum=0 891 | 892 | for (( i=0; i<4; ++i )); do 893 | ((ipnum+=${ip%%.*}*$((256**$((3-i)))))) 894 | ip=${ip#*.} 895 | done 896 | 897 | echo $ipnum 898 | } 899 | 900 | cfping:ntoa() { 901 | echo $(($(($(($((${1}/256))/256))/256))%256)).$(($(($((${1}/256))/256))%256)).$(($((${1}/256))%256)).$((${1}%256)) 902 | } 903 | 904 | cfping:isint() { 905 | (( $1 > 0 )) 2>/dev/null 906 | } 907 | 908 | cfping:isip() { 909 | [[ $1 =~ ^[0-9]+(\.[0-9]+){3}$ ]] 910 | } 911 | 912 | cfping:printip() { 913 | cfping:set start "$1" 914 | cfping:set end "${2:-}" "$(( start + (increment * count) - 1 ))" 915 | 916 | [[ $end -lt $start ]] && \ 917 | cfping:error "start address must be smaller than end address" 918 | 919 | if [[ $cf -eq 1 ]] 920 | then 921 | oldstart=$start 922 | start=$((start+RANDOM%256)) 923 | fi 924 | 925 | while [[ $start -le $end ]]; do 926 | if [[ $cf -eq 1 ]] 927 | then 928 | cfping:ntoa "$start" 929 | oldstart=$(( oldstart + 256 )) 930 | start=$((oldstart+RANDOM%256)) 931 | else 932 | case ${format} in 933 | dec) 934 | echo "$start" 935 | ;; 936 | hex) 937 | printf '%X\n' "$start" 938 | ;; 939 | *) 940 | cfping:ntoa "$start" 941 | ;; 942 | esac 943 | start=$(( start + increment )) 944 | fi 945 | done 946 | } 947 | 948 | cfping:genips() { 949 | local ip ips="" cidr 950 | while IFS= read -r line 951 | do 952 | if [[ -n $line ]] 953 | then 954 | # fping -q -i1 -c5 -p2000 -g 192.168.1.0/24 955 | ip=${line%/*} 956 | cidr=${line#*/} 957 | count=$((2**(32-cidr)-2)) 958 | ips="$ips$(cfping:printip $ip)\n" 959 | fi 960 | done < <(wget --timeout=10 --tries=3 --no-check-certificate "https://www.cloudflare.com/ips-v4" -qO-) 961 | echo -ne "$ips" > ip 962 | if [ -s "ip" ] 963 | then 964 | cfping:info "IPs in file: ${green}ip$normal" 965 | else 966 | cfping:error "connection problem ?" 967 | fi 968 | } 969 | 970 | cfping:testips() { 971 | echo > ip_checked 972 | echo > ip_location 973 | 974 | if [[ $location -eq 1 ]] 975 | then 976 | awk '{print}' ip | xargs -L1 -P"$parallel" sh -c 'colo=$(curl -m 2 -s $0/cdn-cgi/trace | sed -n "s/colo=\(.*\)/\1/p"); if [ -n "$colo" ] ; then echo $0 $colo >> ip_location; fi' 977 | awk '{print $1}' ip_location > ip_checked 978 | else 979 | if [[ $(uname) == "Darwin" ]] 980 | then 981 | awk '{print}' ip | xargs -L1 -P"$((parallel*2))" sh -c 'if nc -z -w 2 -G 2 $0 80 2> /dev/null; then echo $0 >> ip_checked; fi' 982 | else 983 | awk '{print}' ip | xargs -L1 -P"$((parallel*2))" sh -c 'if nc -z -w 2 $0 80 2> /dev/null; then echo $0 >> ip_checked; fi' 984 | fi 985 | fi 986 | if [ -s "ip_checked" ] 987 | then 988 | cfping:info "valid IPs in file: ${green}ip_checked$normal" 989 | else 990 | cfping:error "connection problem ?" 991 | fi 992 | } 993 | 994 | cfping:pingips() { 995 | local best_ips exit_code ip_sorted 996 | fping -q -i"$interval" -c"$packets" -p"$mseconds" -x1 < ip_checked > ip_result 2>&1 || exit_code=$? 997 | 998 | if [[ ${exit_code:-0} -eq 1 ]] || [[ ${exit_code:-0} -eq 3 ]] || [[ ${exit_code:-0} -eq 4 ]] 999 | then 1000 | if [[ $EUID -ne 0 ]] && [[ $interval -lt 10 ]] 1001 | then 1002 | interval=10 1003 | fi 1004 | 1005 | exit_code=0 1006 | fping -q -i"$interval" -c"$packets" -p"$mseconds" < ip_checked > ip_result 2>&1 || exit_code=$? 1007 | 1008 | if [[ $exit_code -gt 1 ]] 1009 | then 1010 | cfping:error "fping error, fping version too old or connection problem ?" 1011 | fi 1012 | fi 1013 | 1014 | awk '{split($5,a,"/");split($8,b,"/"); if($8) printf "%s packets received: %s ping: %s\n",$1,a[2],b[2] | "sort -k4,4rn -k6,6n" }' ip_result > ip_sorted 1015 | if [[ $location -eq 1 ]] 1016 | then 1017 | ip_sorted=$(awk 'NR==FNR{a[$1]=$2;next}{printf "%s location: %s\n",$0,a[$1]}' ip_location ip_sorted) 1018 | echo "$ip_sorted" > ip_sorted 1019 | best_ips=$(awk 'NR < 11 {printf " %s\r\033[18Cpackets received: %s\033[3Cping: %s\033[3Clocation: %s\n",$1,$4,$6,$8}' ip_sorted) 1020 | else 1021 | best_ips=$(awk 'NR < 11 {printf " %s\r\033[18Cpackets received: %s\033[3Cping: %s\n",$1,$4,$6}' ip_sorted) 1022 | fi 1023 | cfping:info "${green}10 BEST IPs$normal\n\n$best_ips\n\n more IPs in file ${green}ip_sorted$normal\n" 1024 | # echo -ne "$ips" | xargs -I {} -P"$parallel" sh -c "ping -c${packets} -q -W2 '{}' > '{}'.out 2>&1" 1025 | } 1026 | 1027 | cfping:speedtestip() { 1028 | curl --resolve "$st_domain:$st_port:$st_ip" "$st_link" -o "$st_ip" -s --connect-timeout 2 --max-time 10 || true 1029 | if [ ! -s "$st_ip" ] 1030 | then 1031 | cfping:error "the domain of the file link must have cname record on cloudflare or try again" 1032 | fi 1033 | if [[ $(uname) == "Darwin" ]] 1034 | then 1035 | stat -f '%N %z' $st_ip | awk '{printf "\n %s\r\033[18Cspeed: %.2f MB/10s\n\n",$1,$2/1024/1024}' 1036 | else 1037 | find $st_ip -type f -printf '%p %s\n' | awk '{printf "\n %s\r\033[18Cspeed: %.2f MB/10s\n\n",$1,$2/1024/1024}' 1038 | fi 1039 | rm -f ${st_ip:-notfound} 1040 | } 1041 | 1042 | cfping:speedtestips() { 1043 | mkdir -p cf_speed_test 1044 | awk 'NR <= '"$st_num"' {print $1}' ip_sorted | xargs -L1 -P"$st_parallel" sh -c 'curl --resolve '"$st_domain:$st_port"':$0 "'"$st_link"'" -o cf_speed_test/$0 -s --connect-timeout 2 --max-time 10 || true' 1045 | cd cf_speed_test 1046 | if [[ $(uname) == "Darwin" ]] 1047 | then 1048 | ip_speed_test=$(find -- * -type f -print0 | xargs -0 stat -f '%N %z' | sort -k2,2rn | awk '{printf "%s %.2f MB\n",$1,$2/1024/1024}') 1049 | # rm -- * 1050 | else 1051 | ip_speed_test=$(find -- * -type f -printf '%p %s\n' | sort -k2,2rn | awk '{printf "%s %.2f MB\n",$1,$2/1024/1024}') 1052 | fi 1053 | cd .. 1054 | rm -rf cf_speed_test 1055 | echo "$ip_speed_test" > ip_speed_test 1056 | ip_speed_test=$(awk 'NR==FNR{a[$1]=$0;next}{printf "%s speed: %s MB/10s\n",a[$1],$2}' ip_sorted ip_speed_test) 1057 | echo "$ip_speed_test" > ip_speed_test 1058 | awk '{if($10) printf " %s\r\033[18Cpackets received: %s\033[3Cping: %s\033[3Clocation: %s\033[3Cspeed: %s MB/10s\n",$1,$4,$6,$8,$10; else printf " %s\r\033[18Cpackets received: %s\033[3Cping: %s\033[3Cspeed: %s MB/10s\n",$1,$4,$6,$8}' ip_speed_test 1059 | cfping:info "${green}Done.$normal\n" 1060 | } 1061 | 1062 | cfping:set() { 1063 | var=$1 1064 | local val=${2:-$3} 1065 | 1066 | case ${var} in 1067 | c) 1068 | var="cf" 1069 | args=0 1070 | ;; 1071 | d) 1072 | var="st" 1073 | args=0 1074 | ;; 1075 | L) 1076 | var="st_link" 1077 | ;; 1078 | N) 1079 | var="st_num" 1080 | ;; 1081 | P) 1082 | var="st_parallel" 1083 | ;; 1084 | I) 1085 | var="st_ip" 1086 | ;; 1087 | s) 1088 | var="mseconds" 1089 | ;; 1090 | p) 1091 | var="packets" 1092 | ;; 1093 | g) 1094 | var="interval" 1095 | ;; 1096 | m) 1097 | var="parallel" 1098 | ;; 1099 | l) 1100 | var="location" 1101 | ;; 1102 | f) 1103 | var="format" 1104 | 1105 | ! echo "${_formats[@]}" | grep -qw "$val" && \ 1106 | cfping:error "invalid format '$val'" 1107 | ;; 1108 | i) 1109 | var="increment" 1110 | 1111 | ! cfping:isint "$val" && \ 1112 | cfping:error "$var must be a positive integer" 1113 | ;; 1114 | n) 1115 | var="count" 1116 | 1117 | ! cfping:isint "$val" && \ 1118 | cfping:error "$var must be a positive integer" 1119 | 1120 | args=1 1121 | ;; 1122 | t) 1123 | var="_this" 1124 | ;; 1125 | start | end) 1126 | if cfping:isip "$val" 1127 | then 1128 | val=$(cfping:aton "$val") 1129 | [[ $cf -eq 1 ]] && val=$((val+1)) 1130 | fi 1131 | 1132 | [[ $cf -eq 1 ]] && val=$((val+1)) 1133 | 1134 | ! cfping:isint "$val" && \ 1135 | cfping:error "bad IP address" 1136 | ;; 1137 | esac 1138 | 1139 | read -r ${var?} <<< "$val" 1140 | } 1141 | 1142 | local _formats=("dec" "dot" "hex") 1143 | local _this="cfping" 1144 | local _version="0.1.4" 1145 | 1146 | local cf=0 1147 | local st=0 1148 | local st_num=100 1149 | local st_link="https://speed.cloudflare.com/__down?bytes=100001000" 1150 | local st_parallel=10 1151 | local st_ip="" 1152 | local mseconds=2000 1153 | local packets=5 1154 | local interval=1 1155 | local parallel=500 1156 | local location=0 1157 | local args=2 1158 | local count=0 1159 | local increment=1 1160 | local format="dot" 1161 | local start 1162 | local end 1163 | 1164 | while getopts "f:i:n:t:p:s:g:m:L:N:P:I:?hvcld" opt; do 1165 | case ${opt} in 1166 | f | i | n | t | p | s | g | m | L | N | P | I) 1167 | cfping:set "$opt" "$OPTARG" 1168 | ;; 1169 | c | l | d) 1170 | cfping:set "$opt" 1 1171 | ;; 1172 | v) 1173 | cfping:error -0 "v$_version" 1174 | ;; 1175 | h | \? | :) 1176 | cfping:help 1177 | ;; 1178 | esac 1179 | done 1180 | shift $((OPTIND -1)) 1181 | 1182 | if [ $# -ne $args ]; then 1183 | cfping:help 1184 | fi 1185 | 1186 | if [[ $cf -eq 1 ]] 1187 | then 1188 | if [[ ! -x $(command -v fping) ]] 1189 | then 1190 | echo 1191 | archs=( 'Mac' 'CentOS' 'Ubuntu/Debian' 'Fedora 22+' 'Arch Linux' ) 1192 | inquirer list_input "What's your system ?" archs arch_selected 1193 | 1194 | case $arch_selected in 1195 | "Mac") 1196 | brew install fping 1197 | ;; 1198 | "CentOS") 1199 | yum -y install fping 1200 | ;; 1201 | "Ubuntu/Debian") 1202 | apt-get -y install fping 1203 | ;; 1204 | "Fedora 22+") 1205 | dnf install fping 1206 | ;; 1207 | "Arch Linux") 1208 | pacman -S fping 1209 | ;; 1210 | esac 1211 | fi 1212 | 1213 | spinner "generating cloudflare IPs" cfping:genips 1214 | spinner "testing IPs, 2 mins" cfping:testips 1215 | spinner "pinging cloudflare IPs, 1 min" cfping:pingips 1216 | 1217 | elif [[ $st -eq 1 ]] 1218 | then 1219 | if [ ! -s "ip_sorted" ] 1220 | then 1221 | cfping:error "no IPs found, run $_this -c" 1222 | fi 1223 | if [[ ${st_link:0:5} == "https" ]] 1224 | then 1225 | st_port=443 1226 | else 1227 | st_port=80 1228 | fi 1229 | st_domain=${st_link#*http://} 1230 | st_domain=${st_domain%%/*} 1231 | st_domain=${st_domain%:*} 1232 | if cfping:isip "$st_domain" 1233 | then 1234 | cfping:error "wrong file link, use domain" 1235 | fi 1236 | if [ -n "$st_ip" ] 1237 | then 1238 | spinner "testing IP $st_ip" cfping:speedtestip 1239 | else 1240 | spinner "speed testing $st_num IPs, 2 mins" cfping:speedtestips 1241 | fi 1242 | else 1243 | cfping:printip "$@" 1244 | fi 1245 | } 1246 | 1247 | if [[ "${BASH_SOURCE[0]}" = "${0}" ]]; then 1248 | cfping -t "$(basename "$0")" "$@" 1249 | fi --------------------------------------------------------------------------------