脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 手把手教你用Go语言打造一款简易TCP端口扫描器

手把手教你用Go语言打造一款简易TCP端口扫描器

2021-01-10 23:22Go语言进阶学习Go进阶者 Golang

这次呢, 咱们来实现一个简单的TCP端口扫描器!也来体验一下黑客的风采!TCP扫描本质就是我们在使用TCP进行连接时,需要知道对方机器的ip:port

 前言

Hey,大家好呀,我是码农,星期八。

这次呢, 咱们来实现一个简单的TCP端口扫描器!

也来体验一下黑客的风采!

TCP扫描本质

我们在使用TCP进行连接时,需要知道对方机器的ip:port

正常握手

连接成功的话,流程如下。

手把手教你用Go语言打造一款简易TCP端口扫描器

连接失败

有正常,就有失败,如果被连接方关闭的话,流程如下。

手把手教你用Go语言打造一款简易TCP端口扫描器

如果有防火墙

还有一种可能是,端口开放,但是防火墙拦截,流程如下。

手把手教你用Go语言打造一款简易TCP端口扫描器

代码

本质理解之后,就可以开始撸代码了。

在Go中,我们通常使用net.Dial进行TCP连接

它就两种情况

  • 成功:返回conn。
  • 失败:err != nil。

普通版

相对来说,刚开始时,我们可能都不是太胆大,都是先写原型,也不考虑性能。

代码

package main 

 

import ( 

    "fmt" 

    "net" 

 

func main() { 

    var ip = "192.168.43.34" 

    for i := 21; i <= 120; i++ { 

        var address = fmt.Sprintf("%s:%d", ip, i) 

        conn, err := net.Dial("tcp", address) 

        if err != nil { 

            fmt.Println(address, "是关闭的"

            continue 

        } 

        conn.Close() 

        fmt.Println(address, "打开"

  } 

执行结果

手把手教你用Go语言打造一款简易TCP端口扫描器

但是这个过程是非常缓慢的。

因为net.Dial如果连接的是未开放的端口,一个端口可能就是20s+,所以,我们为什么学习多线程懂了把!!!

多线程版

上述是通过循环去一个个连接ip:port的,那我们就知道了,在一个个连接的位置,让多个人去干就好了。

所以,多线程如下。

代码

package main 

 

import ( 

    "fmt" 

    "net" 

    "sync" 

    "time" 

 

func main() { 

 

    var begin =time.Now() 

    //wg 

    var wg sync.WaitGroup 

    //ip 

    var ip = "192.168.99.112" 

    //var ip = "192.168.43.34" 

    //循环 

    for j := 21; j <= 65535; j++ { 

        //添加wg 

        wg.Add(1) 

        go func(i int) { 

            //释放wg 

            defer wg.Done() 

            var address = fmt.Sprintf("%s:%d", ip, i) 

            //conn, err := net.DialTimeout("tcp", address, time.Second*10) 

            conn, err := net.Dial("tcp", address) 

            if err != nil { 

                //fmt.Println(address, "是关闭的", err) 

                return 

            } 

            conn.Close() 

            fmt.Println(address, "打开"

        }(j) 

    //等待wg 

    wg.Wait() 

    var elapseTime = time.Now().Sub(begin

    fmt.Println("耗时:", elapseTime) 

执行结果

 手把手教你用Go语言打造一款简易TCP端口扫描器

其实是同时开启了6W多个线程,去扫描每个ip:port。

所以耗时最长的线程结束的时间,就是程序结束的时间。

感觉还行,20s+扫描完6w多个端口!!!

线程池版

上面我们简单粗暴的方式为每个ip:port都创建了一个协程。

虽然在Go中,理论上协程开个几十万个都没问题,但是还是有一些压力的。

所以我们应该采用一种相对节约的方式进行精简代码,一般采用线程池方式。

本次使用的线程池包:gohive

地址:https://github.com/loveleshsharma/gohive

简单介绍

手把手教你用Go语言打造一款简易TCP端口扫描器

代码

package main 

 

//线程池方式 

import ( 

    "fmt" 

    "github.com/loveleshsharma/gohive" 

    "net" 

    "sync" 

    "time" 

 

//wg 

var wg sync.WaitGroup 

 

//地址管道,100容量 

var addressChan = make(chan string, 100) 

 

//工人 

func worker() { 

    //函数结束释放连接 

    defer wg.Done() 

    for { 

        address, ok := <-addressChan 

        if !ok { 

            break 

        } 

        //fmt.Println("address:", address) 

        conn, err := net.Dial("tcp", address) 

        //conn, err := net.DialTimeout("tcp", address, 10) 

        if err != nil { 

            //fmt.Println("close:", address, err) 

            continue 

        } 

        conn.Close() 

        fmt.Println("open:", address) 

func main() { 

    var begin = time.Now() 

    //ip 

    var ip = "192.168.99.112" 

    //线程池大小 

    var pool_size = 70000 

    var pool = gohive.NewFixedSizePool(pool_size) 

 

    //拼接ip:端口 

    //启动一个线程,用于生成ip:port,并且存放到地址管道种 

    go func() { 

        for port := 1; port <= 65535; port++ { 

            var address = fmt.Sprintf("%s:%d", ip, port) 

            //将address添加到地址管道 

            //fmt.Println("<-:",address) 

            addressChan <- address 

        } 

        //发送完关闭 addressChan 管道 

        close(addressChan) 

}() 

    //启动pool_size工人,处理addressChan种的每个地址 

    for work := 0; work < pool_size; work++ { 

        wg.Add(1) 

        pool.Submit(worker) 

    //等待结束 

    wg.Wait() 

    //计算时间 

    var elapseTime = time.Now().Sub(begin

    fmt.Println("耗时:", elapseTime) 

执行结果

手把手教你用Go语言打造一款简易TCP端口扫描器

我设置的线程池大小是7w个,所以也是一下子开启6w多个协程的,但是我们已经可以进行线程大小约束了。

假设现在有这样的去求,有100个ip,需要扫描每个ip开放的端口,如果采用简单粗暴开线程的方式.

那就是100+65535=6552300,600多w个线程,还是比较消耗内存的,可能系统就会崩溃,如果采用线程池方式。

将线程池控制在50w个,或许情况就会好很多。

但是有一点的是,在Go中,线程池通常需要配合chan使用,可能需要不错的基础。

总结

本篇更偏向于乐趣篇,了解一下好玩的玩意。

其实还可以通过net.DialTimeout连接ip:port,这个可以设置超时时间,比如超时5s就判定端口未开放。

此处就不做举例了。

咱们主要使用三种方式来实现功能。

  • 正常版,没有并发,速度很慢。
  • 多协程版,并发,性能很高,但是协程太多可能会崩溃。
  • 协程池版,并发,性能高,协程数量可控。

通常情况下,如果基础可以,更推荐使用协程池方式。

用微笑告诉别人,今天的我比昨天强,今后也一样。

原文地址:https://mp.weixin.qq.com/s/YEBtew4-kPRmAK0VYx7bOA

 

延伸 · 阅读

精彩推荐
  • GolangGo语言实现自动填写古诗词实例代码

    Go语言实现自动填写古诗词实例代码

    这篇文章主要给大家介绍了关于Go语言实现自动填写古诗词的相关资料,这是最近在项目中遇到的一个需求,文中通过示例代码介绍的非常详细,需要的朋...

    FengY5862020-05-14
  • GolangGo语言基础单元测试与性能测试示例详解

    Go语言基础单元测试与性能测试示例详解

    这篇文章主要为大家介绍了Go语言基础单元测试与性能测试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步...

    枫少文7812021-12-05
  • GolangGolang 语言极简类型转换库cast的使用详解

    Golang 语言极简类型转换库cast的使用详解

    本文我们通过 cast.ToString() 函数的使用,简单介绍了cast 的使用方法,除此之外,它还支持很多其他类型,在这没有多多介绍,对Golang 类型转换库 cast相关知...

    Golang语言开发栈6112021-12-02
  • GolangGO语言字符串处理Strings包的函数使用示例讲解

    GO语言字符串处理Strings包的函数使用示例讲解

    这篇文章主要为大家介绍了GO语言字符串处理Strings包的函数使用示例讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加...

    Jeff的技术栈6882022-04-14
  • GolangGo语言range关键字循环时的坑

    Go语言range关键字循环时的坑

    今天小编就为大家分享一篇关于Go语言range关键字循环时的坑,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来...

    benben_20154202020-05-23
  • Golang深入浅析Go中三个点(...)用法

    深入浅析Go中三个点(...)用法

    这篇文章主要介绍了深入浅析Go中三个点(...)用法,需要的朋友可以参考下...

    踏雪无痕SS6472021-11-17
  • Golanggo语言获取系统盘符的方法

    go语言获取系统盘符的方法

    这篇文章主要介绍了go语言获取系统盘符的方法,涉及Go语言调用winapi获取系统硬件信息的技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    无尽海3862020-04-24
  • GolangGolang实现四种负载均衡的算法(随机,轮询等)

    Golang实现四种负载均衡的算法(随机,轮询等)

    本文介绍了示例介绍了Golang 负载均衡的四种实现,主要包括了随机,轮询,加权轮询负载,一致性hash,感兴趣的小伙伴们可以参考一下...

    Gundy_8442021-08-09