├── .gitignore ├── LICENSE ├── README.md ├── clickhouse.go ├── cmd ├── clickhouse │ └── clickhouse_checker.go ├── icmp_scan │ └── fishfinder.go ├── tcp_scan │ └── fishfinder.go └── zmap │ └── zm.sh ├── config ├── ip.sh └── ipv4.txt ├── go.mod ├── go.sum ├── ip.go ├── scanner_icmp.go └── scanner_tcp.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | ip.txt 28 | ip2.txt 29 | 30 | cmd/tcp_scan/tcp_scan 31 | cmd/icmp_scan/icmp_scan 32 | cmd/clickhouse/clickhouse 33 | config/port9000.txt 34 | config/clickhouse.txt 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 smallnest 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 | # echometer 2 | ⚡️ 扫描大量的IP, 计算他们的连通性和时延。(echometer: 回声测深仪,探鱼仪) 3 | -------------------------------------------------------------------------------- /clickhouse.go: -------------------------------------------------------------------------------- 1 | package fishfinding 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "runtime" 7 | "sync" 8 | "time" 9 | 10 | "github.com/ClickHouse/clickhouse-go/v2" 11 | _ "github.com/ClickHouse/clickhouse-go/v2" 12 | "github.com/kataras/golog" 13 | ) 14 | 15 | type ClickHouseChecker struct { 16 | wg *sync.WaitGroup 17 | port int 18 | 19 | input chan string 20 | output chan string 21 | } 22 | 23 | func NewClickHouseChecker(port int, input chan string, output chan string, wg *sync.WaitGroup) *ClickHouseChecker { 24 | s := &ClickHouseChecker{ 25 | port: port, 26 | input: input, 27 | output: output, 28 | wg: wg, 29 | } 30 | return s 31 | } 32 | 33 | func (s *ClickHouseChecker) Check() { 34 | parallel := runtime.NumCPU() 35 | 36 | for i := 0; i < parallel; i++ { 37 | s.wg.Add(1) 38 | go s.check() 39 | } 40 | } 41 | 42 | func (s *ClickHouseChecker) check() { 43 | defer s.wg.Done() 44 | 45 | for ip := range s.input { 46 | if ip == "splitting" || ip == "failed" { 47 | continue 48 | } 49 | 50 | if isClickHouse(ip, s.port) { 51 | s.output <- ip 52 | } 53 | } 54 | } 55 | 56 | func isClickHouse(ip string, port int) bool { 57 | conn, err := clickhouse.Open(&clickhouse.Options{ 58 | Addr: []string{fmt.Sprintf("%s:%d", ip, port)}, 59 | // Auth: clickhouse.Auth{ 60 | // Database: "default", 61 | // Username: "default", 62 | // Password: "", 63 | // }, 64 | Settings: clickhouse.Settings{ 65 | "max_execution_time": 1, 66 | }, 67 | DialTimeout: time.Second, 68 | MaxOpenConns: 1, 69 | MaxIdleConns: 1, 70 | ConnMaxLifetime: time.Duration(1) * time.Minute, 71 | ConnOpenStrategy: clickhouse.ConnOpenInOrder, 72 | BlockBufferSize: 10, 73 | MaxCompressionBuffer: 1024, 74 | }) 75 | if err != nil { 76 | golog.Errorf("open %s:%d failed: %v", ip, port, err) 77 | return false 78 | } 79 | 80 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 81 | defer cancel() 82 | err = conn.Ping(ctx) 83 | if err != nil { 84 | golog.Warnf("failed to connect %s:%d: %v", ip, port, err) 85 | return false 86 | } 87 | 88 | return true 89 | } 90 | -------------------------------------------------------------------------------- /cmd/clickhouse/clickhouse_checker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "strings" 6 | "sync" 7 | "time" 8 | 9 | "github.com/kataras/golog" 10 | "github.com/smallnest/fishfinding" 11 | ) 12 | 13 | var ( 14 | dstPort = flag.Int("d", 9000, "The destination port to use") 15 | ) 16 | 17 | // 嵌入ip.sh 18 | 19 | func main() { 20 | flag.Parse() 21 | 22 | input := make(chan string, 1024) 23 | output := make(chan string, 1024) 24 | var wg sync.WaitGroup 25 | checker := fishfinding.NewClickHouseChecker(*dstPort, input, output, &wg) // 改用 TCP 扫描器 26 | checker.Check() 27 | 28 | golog.Infof("start to check") 29 | 30 | start := time.Now() 31 | // 将待探测的IP发送给send goroutine 32 | go func() { 33 | lines := fishfinding.ReadAvailableIPList("../../config/port9000.txt") 34 | for _, line := range lines { 35 | // [INFO] 2025/01/26 20:58 1.27.222.121 is alive 36 | items := strings.Fields(line) 37 | if len(items) < 6 { 38 | continue 39 | } 40 | line := items[3] 41 | 42 | input <- line 43 | } 44 | close(input) 45 | }() 46 | 47 | total := 0 48 | go func() { 49 | // 接收 checker 的结果, 直到发送之后5秒结束 50 | for ip := range output { 51 | golog.Infof("%s can be accessed", ip) 52 | total++ 53 | } 54 | }() 55 | 56 | wg.Wait() 57 | time.Sleep(time.Second) 58 | 59 | golog.Infof("total: %d, time: %v", total, time.Since(start)) 60 | } 61 | -------------------------------------------------------------------------------- /cmd/icmp_scan/fishfinder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "time" 6 | 7 | "github.com/kataras/golog" 8 | "github.com/smallnest/fishfinding" 9 | ) 10 | 11 | func main() { 12 | flag.Parse() 13 | 14 | input := make(chan []string, 1024) 15 | output := make(chan string, 1024) 16 | scanner := fishfinding.NewICMPScanner(input, output) 17 | 18 | var total int 19 | var alive int 20 | 21 | golog.Infof("start scanning") 22 | 23 | start := time.Now() 24 | // 将待探测的IP发送给send goroutine 25 | go func() { 26 | lines := fishfinding.ReadIPList("../../config/ipv4.txt") 27 | for _, line := range lines { 28 | ips := fishfinding.Cidr2IPList(line) 29 | input <- ips 30 | total += len(ips) 31 | } 32 | close(input) 33 | }() 34 | 35 | // 启动 send goroutine 36 | scanner.Scan() 37 | 38 | // 接收 send goroutine 发送的结果, 直到发送之后5秒结束 39 | for ip := range output { 40 | golog.Infof("%s is alive", ip) 41 | alive++ 42 | } 43 | 44 | golog.Infof("total: %d, alive: %d, time: %v", total, alive, time.Since(start)) 45 | } 46 | -------------------------------------------------------------------------------- /cmd/tcp_scan/fishfinder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "strings" 6 | "time" 7 | 8 | "github.com/kataras/golog" 9 | "github.com/smallnest/fishfinding" 10 | ) 11 | 12 | var ( 13 | srcPort = flag.Int("s", 12345, "The source port to use") 14 | dstPort = flag.Int("d", 9000, "The destination port to use") 15 | ) 16 | 17 | func main() { 18 | flag.Parse() 19 | 20 | input := make(chan string, 1024) 21 | output := make(chan string, 1024) 22 | scanner := fishfinding.NewTCPScanner(*srcPort, *dstPort, input, output) // 改用 TCP 扫描器 23 | 24 | var total int 25 | var alive int 26 | 27 | golog.Infof("start scanning") 28 | 29 | start := time.Now() 30 | // 将待探测的IP发送给send goroutine 31 | go func() { 32 | lines := fishfinding.ReadAvailableIPList("../../config/ip.txt") 33 | for _, line := range lines { 34 | // [INFO] 2025/01/26 20:58 1.27.222.121 is alive 35 | items := strings.Fields(line) 36 | if len(items) != 6 { 37 | continue 38 | } 39 | line := items[3] 40 | input <- line 41 | total++ 42 | } 43 | close(input) 44 | }() 45 | 46 | // 启动 send goroutine 47 | scanner.Scan() 48 | 49 | // 接收 send goroutine 发送的结果, 直到发送之后5秒结束 50 | for ip := range output { 51 | golog.Infof("%s is alive", ip) 52 | alive++ 53 | } 54 | 55 | golog.Infof("total: %d, alive: %d, time: %v", total, alive, time.Since(start)) 56 | } 57 | -------------------------------------------------------------------------------- /cmd/zmap/zm.sh: -------------------------------------------------------------------------------- 1 | !/bin/bash 2 | 3 | date 4 | zmap -i enp2s0 -M icmp_echoscan -l ipv4.txt 5 | date 6 | -------------------------------------------------------------------------------- /config/ip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget -c -O- http://ftp.apnic.net/stats/apnic/delegated-apnic-latest | awk -F '|' '/CN/&&/ipv4/ {print $4 "/" 32-log($5)/log(2)}' | cat > ipv4.txt 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smallnest/fishfinding 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/ClickHouse/clickhouse-go/v2 v2.30.1 7 | github.com/kataras/golog v0.1.12 8 | golang.org/x/net v0.34.0 9 | ) 10 | 11 | require ( 12 | github.com/ClickHouse/ch-go v0.63.1 // indirect 13 | github.com/andybalholm/brotli v1.1.1 // indirect 14 | github.com/go-faster/city v1.0.1 // indirect 15 | github.com/go-faster/errors v0.7.1 // indirect 16 | github.com/google/uuid v1.6.0 // indirect 17 | github.com/kataras/pio v0.0.13 // indirect 18 | github.com/klauspost/compress v1.17.11 // indirect 19 | github.com/paulmach/orb v0.11.1 // indirect 20 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 21 | github.com/pkg/errors v0.9.1 // indirect 22 | github.com/segmentio/asm v1.2.0 // indirect 23 | github.com/shopspring/decimal v1.4.0 // indirect 24 | go.opentelemetry.io/otel v1.26.0 // indirect 25 | go.opentelemetry.io/otel/trace v1.26.0 // indirect 26 | golang.org/x/sys v0.29.0 // indirect 27 | gopkg.in/yaml.v3 v3.0.1 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ClickHouse/ch-go v0.63.1 h1:s2JyZvWLTCSAGdtjMBBmAgQQHMco6pawLJMOXi0FODM= 2 | github.com/ClickHouse/ch-go v0.63.1/go.mod h1:I1kJJCL3WJcBMGe1m+HVK0+nREaG+JOYYBWjrDrF3R0= 3 | github.com/ClickHouse/clickhouse-go/v2 v2.30.1 h1:Dy0n0l+cMbPXs8hFkeeWGaPKrB+MDByUNQBSmRO3W6k= 4 | github.com/ClickHouse/clickhouse-go/v2 v2.30.1/go.mod h1:szk8BMoQV/NgHXZ20ZbwDyvPWmpfhRKjFkc6wzASGxM= 5 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 6 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= 11 | github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= 12 | github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= 13 | github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= 14 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 15 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 16 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 17 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 18 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 19 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 20 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 21 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 22 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 23 | github.com/kataras/golog v0.1.12 h1:Bu7I/G4ilJlbfzjmU39O9N+2uO1pBcMK045fzZ4ytNg= 24 | github.com/kataras/golog v0.1.12/go.mod h1:wrGSbOiBqbQSQznleVNX4epWM8rl9SJ/rmEacl0yqy4= 25 | github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM= 26 | github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= 27 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 28 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 29 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 30 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 31 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 32 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 33 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 34 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 35 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 36 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 37 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 38 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 39 | github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= 40 | github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= 41 | github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= 42 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 43 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 44 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 45 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 46 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 47 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 48 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 49 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 50 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 51 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 52 | github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 53 | github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 54 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 55 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 56 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 57 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 58 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 59 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 60 | github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= 61 | github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= 62 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 63 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 64 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 65 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 66 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 67 | go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= 68 | go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= 69 | go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= 70 | go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= 71 | go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= 72 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 73 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 74 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 75 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 76 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 77 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 78 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 79 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 80 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 81 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 82 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 83 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 84 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 85 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 86 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 87 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 88 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 89 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 90 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 91 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 92 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 93 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 94 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 96 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 97 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 98 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 99 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 100 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 101 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 102 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 103 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 104 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 105 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 106 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 107 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 108 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 109 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 110 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 111 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 112 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 113 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 114 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 115 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 116 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 117 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 118 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 119 | -------------------------------------------------------------------------------- /ip.go: -------------------------------------------------------------------------------- 1 | package fishfinding 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "strings" 7 | "time" 8 | 9 | "github.com/kataras/golog" 10 | "golang.org/x/net/bpf" 11 | ) 12 | 13 | func ReadIPList(fileName string) []string { 14 | golog.Info("reading " + fileName) 15 | data, err := os.ReadFile(fileName) 16 | if err != nil { 17 | golog.Fatal(err) 18 | } 19 | 20 | golog.Info("splitting the IPs") 21 | return strings.Split(string(data), "\n") 22 | } 23 | 24 | func ReadAvailableIPList(fileName string) []string { 25 | golog.Info("reading " + fileName) 26 | data, err := os.ReadFile(fileName) 27 | if err != nil { 28 | golog.Fatal(err) 29 | } 30 | 31 | golog.Info("splitting the IPs") 32 | return strings.Split(string(data), "\n") 33 | } 34 | 35 | func GetLocalIP() string { 36 | conn, err := net.DialTimeout("udp", "114.114.114.114:53", 10*time.Second) 37 | if err != nil { 38 | golog.Fatalf("failed to get local IP address: %v", err) 39 | } 40 | localIP := conn.LocalAddr().String() 41 | conn.Close() 42 | 43 | host, _, _ := net.SplitHostPort(localIP) 44 | 45 | return host 46 | } 47 | 48 | func Cidr2IPList(cidr string) []string { 49 | if cidr == "" { 50 | return nil 51 | } 52 | 53 | var ips []string 54 | ip, ipnet, err := net.ParseCIDR(cidr) 55 | if err != nil { 56 | golog.Errorf("failed to parse CIDR %s: %v", cidr, err) 57 | return nil 58 | } 59 | for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); ip = incIP(ip) { 60 | ips = append(ips, ip.String()) 61 | } 62 | return ips 63 | } 64 | 65 | func incIP(ip net.IP) net.IP { 66 | return int2IP(ip2Int(ip) + 1) 67 | } 68 | 69 | func ip2Int(ip net.IP) uint32 { 70 | ip = ip.To4() 71 | if ip == nil { 72 | return 0 73 | } 74 | return (uint32(ip[0]) << 24) | (uint32(ip[1]) << 16) | (uint32(ip[2]) << 8) | uint32(ip[3]) 75 | } 76 | 77 | func int2IP(ipInt uint32) net.IP { 78 | ip := make(net.IP, 4) 79 | ip[0] = byte(ipInt >> 24) 80 | ip[1] = byte(ipInt >> 16) 81 | ip[2] = byte(ipInt >> 8) 82 | ip[3] = byte(ipInt) 83 | return ip 84 | } 85 | 86 | type Filter []bpf.Instruction 87 | 88 | func createEmptyFilter() Filter { 89 | filter := Filter{ 90 | bpf.RetConstant{Val: 0x0}, 91 | } 92 | return filter 93 | } 94 | -------------------------------------------------------------------------------- /scanner_icmp.go: -------------------------------------------------------------------------------- 1 | package fishfinding 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "time" 7 | 8 | "github.com/kataras/golog" 9 | log "github.com/kataras/golog" 10 | "golang.org/x/net/bpf" 11 | "golang.org/x/net/icmp" 12 | "golang.org/x/net/ipv4" 13 | ) 14 | 15 | const ( 16 | protocolICMP = 1 17 | ) 18 | 19 | type ICMPScanner struct { 20 | src net.IP 21 | 22 | input chan []string 23 | output chan string 24 | } 25 | 26 | // 调大缓存区 27 | // sysctl net.core.rmem_max 28 | // sysctl net.core.wmem_max 29 | 30 | func NewICMPScanner(input chan []string, output chan string) *ICMPScanner { 31 | localIP := GetLocalIP() 32 | s := &ICMPScanner{ 33 | input: input, 34 | output: output, 35 | src: net.ParseIP(localIP), 36 | } 37 | return s 38 | } 39 | 40 | func (s *ICMPScanner) Scan() { 41 | go s.recv() 42 | go s.send(s.input) 43 | } 44 | 45 | // send sends a single ICMP echo request packet for each ip in the input channel. 46 | func (s *ICMPScanner) send(input chan []string) error { 47 | defer func() { 48 | time.Sleep(5 * time.Second) 49 | close(s.output) 50 | golog.Infof("send goroutine exit") 51 | }() 52 | 53 | id := os.Getpid() & 0xffff 54 | 55 | // 创建 ICMP 连接 56 | conn, err := icmp.ListenPacket("ip4:icmp", s.src.String()) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | defer conn.Close() 61 | 62 | // 不负责接收数据 63 | filter := createEmptyFilter() 64 | if assembled, err := bpf.Assemble(filter); err == nil { 65 | conn.IPv4PacketConn().SetBPF(assembled) 66 | } 67 | 68 | seq := uint16(0) 69 | for ips := range input { 70 | for _, ip := range ips { 71 | dst, err := net.ResolveIPAddr("ip", ip) 72 | if err != nil { 73 | golog.Errorf("failed to resolve IP address: %v", err) 74 | continue 75 | } 76 | 77 | // 构造 ICMP 报文 78 | msg := &icmp.Message{ 79 | Type: ipv4.ICMPTypeEcho, 80 | Code: 0, 81 | Body: &icmp.Echo{ 82 | ID: id, 83 | Seq: int(seq), 84 | Data: []byte("Hello, are you there!"), 85 | }, 86 | } 87 | msgBytes, err := msg.Marshal(nil) 88 | if err != nil { 89 | golog.Errorf("failed to marshal ICMP message: %v", err) 90 | } 91 | 92 | // 发送 ICMP 报文 93 | _, err = conn.WriteTo(msgBytes, dst) 94 | if err != nil { 95 | golog.Errorf("failed to send ICMP message: %v", err) 96 | } 97 | seq++ 98 | } 99 | } 100 | 101 | return nil 102 | } 103 | 104 | // recv receives ICMP echo reply packets and sends the source IP to the output channel. 105 | func (s *ICMPScanner) recv() error { 106 | defer recover() 107 | 108 | id := os.Getpid() & 0xffff 109 | 110 | // 创建 ICMP 连接 111 | conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") 112 | if err != nil { 113 | log.Fatal(err) 114 | } 115 | defer conn.Close() 116 | 117 | // 接收 ICMP 报文 118 | reply := make([]byte, 1500) 119 | for { 120 | n, peer, err := conn.ReadFrom(reply) 121 | if err != nil { 122 | log.Fatal(err) 123 | } 124 | 125 | // 解析 ICMP 报文 126 | msg, err := icmp.ParseMessage(protocolICMP, reply[:n]) 127 | if err != nil { 128 | golog.Errorf("failed to parse ICMP message: %v", err) 129 | continue 130 | } 131 | 132 | // 打印结果 133 | switch msg.Type { 134 | case ipv4.ICMPTypeEchoReply: 135 | echoReply, ok := msg.Body.(*icmp.Echo) 136 | if !ok { 137 | continue 138 | } 139 | if echoReply.ID == id { 140 | s.output <- peer.String() 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /scanner_tcp.go: -------------------------------------------------------------------------------- 1 | package fishfinding 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "time" 7 | 8 | "github.com/kataras/golog" 9 | "golang.org/x/net/bpf" 10 | "golang.org/x/net/ipv4" 11 | ) 12 | 13 | const ( 14 | tcpHeaderLength = 20 15 | ) 16 | 17 | type TCPScanner struct { 18 | src net.IP 19 | srcPort int 20 | dstPort int 21 | input chan string 22 | output chan string 23 | } 24 | 25 | func NewTCPScanner(srcPort, dstPort int, input chan string, output chan string) *TCPScanner { 26 | localIP := GetLocalIP() 27 | s := &TCPScanner{ 28 | input: input, 29 | output: output, 30 | src: net.ParseIP(localIP).To4(), 31 | srcPort: srcPort, 32 | dstPort: dstPort, 33 | } 34 | return s 35 | } 36 | 37 | func (s *TCPScanner) Scan() { 38 | go s.recv() 39 | go s.send(s.input) 40 | } 41 | 42 | func (s *TCPScanner) send(input chan string) error { 43 | defer func() { 44 | time.Sleep(5 * time.Second) 45 | close(s.output) 46 | golog.Infof("send goroutine exit") 47 | }() 48 | 49 | // 创建原始套接字 50 | conn, err := net.ListenPacket("ip4:tcp", s.src.To4().String()) 51 | if err != nil { 52 | golog.Fatal(err) 53 | } 54 | defer conn.Close() 55 | 56 | pconn := ipv4.NewPacketConn(conn) 57 | // 不接收数据 58 | filter := createEmptyFilter() 59 | if assembled, err := bpf.Assemble(filter); err == nil { 60 | pconn.SetBPF(assembled) 61 | } 62 | 63 | seq := uint32(os.Getpid()) 64 | for ip := range input { 65 | dstIP := net.ParseIP(ip) 66 | if dstIP == nil { 67 | golog.Errorf("failed to resolve IP address %s", ip) 68 | continue 69 | } 70 | 71 | // 构造 TCP SYN 包 72 | tcpHeader := &TCPHeader{ 73 | Source: uint16(s.srcPort), // 源端口 74 | Destination: uint16(s.dstPort), // 目标端口(这里探测80端口) 75 | SeqNum: seq, 76 | AckNum: 0, 77 | Flags: 0x002, // SYN 78 | Window: 65535, 79 | Checksum: 0, 80 | Urgent: 0, 81 | } 82 | 83 | // 计算校验和 84 | tcpHeader.Checksum = tcpChecksum(tcpHeader, s.src, dstIP) 85 | 86 | // 序列化 TCP 头 87 | packet := tcpHeader.Marshal() 88 | 89 | // 发送 TCP SYN 包 90 | _, err = conn.WriteTo(packet, &net.IPAddr{IP: dstIP}) 91 | if err != nil { 92 | golog.Errorf("failed to send TCP packet: %v", err) 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func (s *TCPScanner) recv() error { 100 | defer recover() 101 | 102 | // 创建原始套接字 103 | conn, err := net.ListenIP("ip4:tcp", &net.IPAddr{IP: s.src}) 104 | if err != nil { 105 | golog.Fatal(err) 106 | } 107 | defer conn.Close() 108 | 109 | pconn := ipv4.NewPacketConn(conn) 110 | if err := pconn.SetControlMessage(ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, true); err != nil { 111 | golog.Fatalf("set control message error: %v\n", err) 112 | } 113 | 114 | seq := uint32(os.Getpid()) + 1 115 | 116 | buf := make([]byte, 1024) 117 | for { 118 | n, peer, err := conn.ReadFrom(buf) 119 | if err != nil { 120 | golog.Errorf("failed to read: %v", err) 121 | continue 122 | } 123 | 124 | if n < tcpHeaderLength { 125 | continue 126 | } 127 | 128 | // 解析 TCP 头 129 | tcpHeader := ParseTCPHeader(buf[:n]) 130 | 131 | if tcpHeader.Destination != uint16(s.srcPort) || tcpHeader.Source != uint16(s.dstPort) { 132 | continue 133 | } 134 | 135 | // golog.Info("peer: %s, flags: %d", peer.String(), tcpHeader.Flags) 136 | 137 | // 检查是否是 SYN+ACK, 同时检查ACK是否和发送的seq对应 138 | if tcpHeader.Flags == 0x012 && tcpHeader.AckNum == seq { // SYN + ACK 139 | s.output <- peer.String() 140 | } 141 | } 142 | } 143 | 144 | // TCP 头结构 145 | type TCPHeader struct { 146 | Source uint16 147 | Destination uint16 148 | SeqNum uint32 149 | AckNum uint32 150 | Offset uint8 151 | Flags uint8 152 | Window uint16 153 | Checksum uint16 154 | Urgent uint16 155 | } 156 | 157 | func (h *TCPHeader) Marshal() []byte { 158 | // TCP 头序列化实现 159 | buf := make([]byte, tcpHeaderLength) 160 | 161 | // 填充 TCP 头字段 162 | buf[0] = byte(h.Source >> 8) 163 | buf[1] = byte(h.Source) 164 | buf[2] = byte(h.Destination >> 8) 165 | buf[3] = byte(h.Destination) 166 | buf[4] = byte(h.SeqNum >> 24) 167 | buf[5] = byte(h.SeqNum >> 16) 168 | buf[6] = byte(h.SeqNum >> 8) 169 | buf[7] = byte(h.SeqNum) 170 | buf[8] = byte(h.AckNum >> 24) 171 | buf[9] = byte(h.AckNum >> 16) 172 | buf[10] = byte(h.AckNum >> 8) 173 | buf[11] = byte(h.AckNum) 174 | buf[12] = byte(tcpHeaderLength << 2) 175 | buf[13] = h.Flags 176 | buf[14] = byte(h.Window >> 8) 177 | buf[15] = byte(h.Window) 178 | buf[16] = byte(h.Checksum >> 8) 179 | buf[17] = byte(h.Checksum) 180 | buf[18] = byte(h.Urgent >> 8) 181 | buf[19] = byte(h.Urgent) 182 | 183 | return buf 184 | } 185 | 186 | func ParseTCPHeader(data []byte) *TCPHeader { 187 | header := &TCPHeader{ 188 | Source: uint16(data[0])<<8 | uint16(data[1]), 189 | Destination: uint16(data[2])<<8 | uint16(data[3]), 190 | SeqNum: uint32(data[4])<<24 | uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]), 191 | AckNum: uint32(data[8])<<24 | uint32(data[9])<<16 | uint32(data[10])<<8 | uint32(data[11]), 192 | Offset: data[12] >> 4, 193 | Flags: data[13], 194 | Window: uint16(data[14])<<8 | uint16(data[15]), 195 | Checksum: uint16(data[16])<<8 | uint16(data[17]), 196 | Urgent: uint16(data[18])<<8 | uint16(data[19]), 197 | } 198 | return header 199 | } 200 | 201 | // TCP 伪头部结构,用于校验和计算 202 | type TCPPseudoHeader struct { 203 | SrcIP [4]byte 204 | DstIP [4]byte 205 | Zero byte 206 | Protocol byte 207 | TCPLength uint16 208 | } 209 | 210 | func tcpChecksum(header *TCPHeader, srcIP, dstIP net.IP) uint16 { 211 | // 创建伪头部 212 | pseudo := TCPPseudoHeader{ 213 | Protocol: 6, // TCP protocol number 214 | TCPLength: tcpHeaderLength, 215 | } 216 | copy(pseudo.SrcIP[:], srcIP.To4()) 217 | copy(pseudo.DstIP[:], dstIP.To4()) 218 | 219 | // 序列化 TCP 头 220 | tcpData := header.Marshal() 221 | 222 | // 计算总长度并创建缓冲区 223 | totalLen := uint32(len(tcpData)) + 12 // 12 是伪头部长度 224 | buf := make([]byte, totalLen) 225 | 226 | // 复制伪头部数据 227 | copy(buf[0:4], pseudo.SrcIP[:]) 228 | copy(buf[4:8], pseudo.DstIP[:]) 229 | buf[8] = pseudo.Zero 230 | buf[9] = pseudo.Protocol 231 | buf[10] = byte(pseudo.TCPLength >> 8) 232 | buf[11] = byte(pseudo.TCPLength) 233 | 234 | // 复制 TCP 头数据 235 | copy(buf[12:], tcpData) 236 | 237 | // 计算校验和 238 | var sum uint32 239 | for i := 0; i < len(buf)-1; i += 2 { 240 | sum += uint32(buf[i])<<8 | uint32(buf[i+1]) 241 | } 242 | 243 | // 如果长度为奇数,处理最后一个字节 244 | if len(buf)%2 == 1 { 245 | sum += uint32(buf[len(buf)-1]) << 8 246 | } 247 | 248 | // 将高16位加到低16位 249 | for sum>>16 != 0 { 250 | sum = (sum & 0xffff) + (sum >> 16) 251 | } 252 | 253 | // 返回校验和的反码 254 | return ^uint16(sum) 255 | } 256 | --------------------------------------------------------------------------------