From 5c67f34e71efc4020aba9bac68693e45d13b267d Mon Sep 17 00:00:00 2001 From: Spedoske <52339623+Spedoske@users.noreply.github.com> Date: Thu, 9 Jul 2020 10:52:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=8B=E8=BD=BD=E6=B5=8B?= =?UTF-8?q?=E9=80=9F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .goreleaser.yml | 41 ++++++++++++++++ IPRangeLoader.go | 2 +- go.mod | 5 +- main.go | 98 +++++++++++++++++--------------------- tcping.go | 100 +++++++++++++++++++++++++++++++++------ util.go | 121 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 296 insertions(+), 71 deletions(-) create mode 100644 .goreleaser.yml create mode 100644 util.go diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..34efefe --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,41 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # you may remove this if you don't use vgo + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... +builds: +- env: + - CGO_ENABLED=0 + id: "CloudflareScanner" + binary: "CloudflareScanner" + goos: + - darwin + - freebsd + - linux + - windows + goarch: + - 386 + - amd64 + - arm + #hooks: + #post: ./compile.bat "{{ dir .Path }}" +archives: +- replacements: + darwin: MacOS + linux: Linux + windows: Windows + 386: x86 + amd64: x64 +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "v1.1.0" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' \ No newline at end of file diff --git a/IPRangeLoader.go b/IPRangeLoader.go index 1a66031..e06dffd 100644 --- a/IPRangeLoader.go +++ b/IPRangeLoader.go @@ -21,7 +21,7 @@ func loadFirstIPOfRangeFromFile() []net.IPAddr { if err != nil { log.Fatal(err) } - firstIP[15]=ipEndWith + firstIP[15] = ipEndWith for IPRange.Contains(firstIP) { firstIPCopy := make([]byte, len(firstIP)) copy(firstIPCopy, firstIP) diff --git a/go.mod b/go.mod index c16100f..31dec57 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module CloudflareIPScanner go 1.14 -require github.com/cheggaaa/pb/v3 v3.0.4 +require ( + github.com/VividCortex/ewma v1.1.1 + github.com/cheggaaa/pb/v3 v3.0.4 +) diff --git a/main.go b/main.go index 57d8f24..c5bc1c9 100644 --- a/main.go +++ b/main.go @@ -1,79 +1,67 @@ package main import ( - "encoding/csv" "fmt" "github.com/cheggaaa/pb/v3" - "log" - "os" + "sort" "sync" + "time" ) -func ExportCsv(filePath string, data [][]string) { - fp, err := os.Create(filePath) - if err != nil { - log.Fatalf("创建文件["+filePath+"]句柄失败,%v", err) - return - } - defer fp.Close() - w := csv.NewWriter(fp) //创建一个新的写入文件流 - w.WriteAll(data) - w.Flush() -} - -var pingTime int -var pingRoutine int -const ipEndWith uint8 = 1 -type progressEvent int -const ( - NoAvailableIPFound progressEvent = iota - AvailableIPFound - NormalPing -) - -func handleProgressGenerator(pb *pb.ProgressBar)func (e progressEvent){ - return func(e progressEvent) { - switch e { - case NoAvailableIPFound: - pb.Add(pingTime) - case AvailableIPFound: - pb.Add(failTime) - case NormalPing: - pb.Increment() - } - } -} - -func handleUserInput(){ - fmt.Println("请输入扫描协程数(数字越大越快,默认100):") +func handleUserInput() { + fmt.Println("请输入扫描协程数(数字越大越快,默认400):") fmt.Scanln(&pingRoutine) - if pingRoutine<=0{ - pingRoutine=100 + if pingRoutine <= 0 { + pingRoutine = 400 } fmt.Println("请输入tcping次数(默认10):") fmt.Scanln(&pingTime) - if pingTime<=0{ - pingTime=10 + if pingTime <= 0 { + pingTime = 10 + } + fmt.Println("请输入要测试的下载节点个数(默认10):") + fmt.Scanln(&downloadTestCount) + if downloadTestCount <= 0 { + downloadTestCount = 10 } + fmt.Println("请输入下载测试时间(默认10,单位为秒):") + var downloadSecond int64 + fmt.Scanln(&downloadSecond) + if downloadSecond <= 0 { + downloadSecond = 10 + } + downloadTestTime = time.Duration(downloadSecond) * time.Second } -func main(){ +func main() { + initipEndWith() handleUserInput() - ips:=loadFirstIPOfRangeFromFile() - pingCount:=len(ips)*pingTime + ips := loadFirstIPOfRangeFromFile() + pingCount := len(ips) * pingTime bar := pb.StartNew(pingCount) var wg sync.WaitGroup var mu sync.Mutex - var data = make([][]string,0) - data = append(data,[]string{"IP Address","Ping received","Ping time"}) - control := make(chan bool,pingRoutine) - for _,ip :=range ips{ + var data = make([]CloudflareIPData, 0) + + fmt.Println("开始tcping") + + control := make(chan bool, pingRoutine) + for _, ip := range ips { wg.Add(1) - control<-false - handleProgress:=handleProgressGenerator(bar) - go tcpingGoroutine(&wg,&mu,ip,pingTime, &data,control,handleProgress) + control <- false + handleProgress := handleProgressGenerator(bar) + go tcpingGoroutine(&wg, &mu, ip, pingTime, &data, control, handleProgress) } wg.Wait() bar.Finish() - ExportCsv("./result.csv",data) + bar = pb.StartNew(downloadTestCount) + sort.Sort(CloudflareIPDataSet(data)) + fmt.Println("开始下载测速") + for i := 0; i < downloadTestCount; i++ { + _, speed := DownloadSpeedHandler(data[i].ip) + data[i].downloadSpeed = speed + bar.Add(1) + } + bar.Finish() + ExportCsv("./result.csv", data) } diff --git a/tcping.go b/tcping.go index 35eb44b..9d43090 100644 --- a/tcping.go +++ b/tcping.go @@ -1,34 +1,34 @@ package main import ( + "context" + "github.com/VividCortex/ewma" + "io" "net" + "net/http" "strconv" "sync" "time" ) -const defaultTcpPort = 443 -const tcpConnectTimeout = time.Second * 1 -const failTime = 4 - -//bool connectionSucceed float64 time -func tcping(ip net.IPAddr) (bool, float64) { +//bool connectionSucceed float32 time +func tcping(ip net.IPAddr) (bool, float32) { startTime := time.Now() conn, err := net.DialTimeout("tcp", ip.String()+":"+strconv.Itoa(defaultTcpPort), tcpConnectTimeout) if err != nil { return false, 0 } else { var endTime = time.Since(startTime) - var duration = float64(endTime.Microseconds()) / 1000.0 + var duration = float32(endTime.Microseconds()) / 1000.0 _ = conn.Close() return true, duration } } //pingReceived pingTotalTime -func checkConnection(ip net.IPAddr) (int, float64) { +func checkConnection(ip net.IPAddr) (int, float32) { pingRecv := 0 - pingTime := 0.0 + var pingTime float32 = 0.0 for i := 1; i <= failTime; i++ { pingSucceed, pingTimeCurrent := tcping(ip) if pingSucceed { @@ -40,10 +40,10 @@ func checkConnection(ip net.IPAddr) (int, float64) { } //return Success packetRecv averagePingTime specificIPAddr -func tcpingHandler(ip net.IPAddr, pingCount int, progressHandler func(e progressEvent)) (bool, int, float64, net.IPAddr) { +func tcpingHandler(ip net.IPAddr, pingCount int, progressHandler func(e progressEvent)) (bool, int, float32, net.IPAddr) { ipCanConnect := false pingRecv := 0 - pingTime := 0.0 + var pingTime float32 = 0.0 for !ipCanConnect { pingRecvCurrent, pingTimeCurrent := checkConnection(ip) if pingRecvCurrent != 0 { @@ -68,20 +68,92 @@ func tcpingHandler(ip net.IPAddr, pingCount int, progressHandler func(e progress pingTime += pingTimeCurrent } } - return true, pingRecv, pingTime / float64(pingRecv), ip + return true, pingRecv, pingTime / float32(pingRecv), ip } else { progressHandler(NoAvailableIPFound) return false, 0, 0, net.IPAddr{} } } -func tcpingGoroutine(wg *sync.WaitGroup, mutex *sync.Mutex, ip net.IPAddr, pingCount int, csv *[][]string, control chan bool, progressHandler func(e progressEvent)) { +func tcpingGoroutine(wg *sync.WaitGroup, mutex *sync.Mutex, ip net.IPAddr, pingCount int, csv *[]CloudflareIPData, control chan bool, progressHandler func(e progressEvent)) { defer wg.Done() success, pingRecv, pingTimeAvg, currentIP := tcpingHandler(ip, pingCount, progressHandler) if success { mutex.Lock() - *csv = append(*csv, []string{currentIP.String(), strconv.Itoa(pingRecv), strconv.FormatFloat(pingTimeAvg, 'f', 4, 64)}) + var cfdata CloudflareIPData + cfdata.ip = currentIP + cfdata.pingReceived = pingRecv + cfdata.pingTime = pingTimeAvg + cfdata.pingCount = pingCount + *csv = append(*csv, cfdata) mutex.Unlock() } <-control } + +func GetDialContextByAddr(fakeSourceAddr string) func(ctx context.Context, network, address string) (net.Conn, error) { + return func(ctx context.Context, network, address string) (net.Conn, error) { + c, e := (&net.Dialer{}).DialContext(ctx, network, fakeSourceAddr) + return c, e + } +} + +//bool : can download,float32 downloadSpeed +func DownloadSpeedHandler(ip net.IPAddr) (bool, float32) { + var client = http.Client{ + Transport: nil, + CheckRedirect: nil, + Jar: nil, + Timeout: 0, + } + client.Transport = &http.Transport{ + DialContext: GetDialContextByAddr(ip.String() + ":443"), + } + response, err := client.Get(url) + + if err != nil { + return false, 0 + } else { + defer func() { _ = response.Body.Close() }() + if response.StatusCode == 200 { + timeStart := time.Now() + timeEnd := timeStart.Add(downloadTestTime) + + contentLength := response.ContentLength + buffer := make([]byte, downloadBufferSize) + + var contentRead int64 = 0 + var timeSlice = downloadTestTime / 100 + var timeCounter = 1 + var lastContentRead int64 = 0 + + var nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter)) + e := ewma.NewMovingAverage() + + for ; contentLength != contentRead; { + var currentTime = time.Now() + if currentTime.After(nextTime) { + timeCounter += 1 + nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter)) + e.Add(float64(contentRead - lastContentRead)) + lastContentRead = contentRead + } + if currentTime.After(timeEnd) { + break + } + bufferRead, err := response.Body.Read(buffer) + contentRead += int64(bufferRead) + if err != nil { + if err != io.EOF { + break + } else { + e.Add(float64(contentRead-lastContentRead) / (float64(nextTime.Sub(currentTime)) / float64(timeSlice))) + } + } + } + return true, float32(e.Value()) / (float32(downloadTestTime.Seconds()) / 100) + } else { + return false, 0 + } + } +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..1b43093 --- /dev/null +++ b/util.go @@ -0,0 +1,121 @@ +package main + +import ( + "encoding/csv" + "github.com/cheggaaa/pb/v3" + "log" + "math/rand" + "net" + "os" + "strconv" + "time" +) + +type CloudflareIPData struct { + ip net.IPAddr + pingCount int + pingReceived int + recvRate float32 + downloadSpeed float32 + pingTime float32 +} + +func (cf *CloudflareIPData) getRecvRate() float32 { + if cf.recvRate == 0 { + cf.recvRate = float32(cf.pingReceived) / float32(cf.pingCount) + } + return cf.recvRate +} + +func ExportCsv(filePath string, data []CloudflareIPData) { + fp, err := os.Create(filePath) + if err != nil { + log.Fatalf("创建文件["+filePath+"]句柄失败,%v", err) + return + } + defer fp.Close() + w := csv.NewWriter(fp) //创建一个新的写入文件流 + w.Write([]string{"IP Address", "Ping count", "Ping received", "Ping received rate", "Ping time", "Download Speed (MB/s)"}) + w.WriteAll(convertToString(data)) + w.Flush() +} + +//"IP Address","Ping Count","Ping received","Ping received rate","Ping time","Download speed" + +func (cf *CloudflareIPData) toString() []string { + result := make([]string, 6) + result[0] = cf.ip.String() + result[1] = strconv.Itoa(cf.pingCount) + result[2] = strconv.Itoa(cf.pingReceived) + result[3] = strconv.FormatFloat(float64(cf.getRecvRate()), 'f', 4, 32) + result[4] = strconv.FormatFloat(float64(cf.pingTime), 'f', 4, 32) + result[5] = strconv.FormatFloat(float64(cf.downloadSpeed)/1024/1024, 'f', 4, 32) + return result +} + +func convertToString(data []CloudflareIPData) [][]string { + result := make([][]string, 0) + for _, v := range data { + result = append(result, v.toString()) + } + return result +} + +var pingTime int +var pingRoutine int + +var ipEndWith uint8 = 0 + +type progressEvent int + +const ( + NoAvailableIPFound progressEvent = iota + AvailableIPFound + NormalPing +) + +const url string = "https://apple.freecdn.workers.dev/105/media/us/iphone-11-pro/2019/3bd902e4-0752-4ac1-95f8-6225c32aec6d/films/product/iphone-11-pro-product-tpl-cc-us-2019_1280x720h.mp4" + +var downloadTestTime time.Duration + +const downloadBufferSize = 1024 + +var downloadTestCount int + +const defaultTcpPort = 443 +const tcpConnectTimeout = time.Second * 1 +const failTime = 4 + +type CloudflareIPDataSet []CloudflareIPData + +func initipEndWith() { + ipEndWith = uint8(rand.Intn(254) + 1) +} + +func handleProgressGenerator(pb *pb.ProgressBar) func(e progressEvent) { + return func(e progressEvent) { + switch e { + case NoAvailableIPFound: + pb.Add(pingTime) + case AvailableIPFound: + pb.Add(failTime) + case NormalPing: + pb.Increment() + } + } +} + +func (cfs CloudflareIPDataSet) Len() int { + return len(cfs) +} + +func (cfs CloudflareIPDataSet) Less(i, j int) bool { + if (cfs)[i].getRecvRate() != cfs[j].getRecvRate() { + return cfs[i].getRecvRate() > cfs[j].getRecvRate() + } + return cfs[i].pingTime < cfs[j].pingTime +} + +func (cfs CloudflareIPDataSet) Swap(i, j int) { + cfs[i], cfs[j] = cfs[j], cfs[i] +}