NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

Go 实现简单 TCP 扫描器

  • 2019-11-14
  • 本文字数:2364 字

    阅读完需:约 8 分钟

Go实现简单TCP扫描器

今天为大家分享一篇关于 Go 实现 TCP 扫描器的文章,如果大家对 Go 编写扫描器感兴趣,可以看下本篇文章。希望能对大家有所帮助。


Go 在网络应用编程方面堪称完美。它自带的标准库也很优秀,在开发过程中可以给予我们很多帮助。


在本文中,我们将会用 Go 写一个简单的 TCP 扫描器。整个程序的代码在 50 行以内。在我们开始动手之前,先介绍一些理论知识。


不得不说,TCP 是比我们介绍的要复杂的多,但是我们只介绍一点基础知识。TCP 的握手有三个过程。首先,客户端发送一个 syn 的包,表示建立回话的开始。如果客户端收到超时,说明端口可能在防火墙后面,



第二,如果服务端应答 syn-ack 包,意味着这个端口是打开的,否则会返回 rst 包。最后,客户端需要另外发送一个 ack 包。从这时起,连接就已经建立。



我们 TCP 扫描器第一步先实现单个端口的测试。使用标准库中的 net.Dial 函数,该函数接收两个参数:协议和测试地址(带端口号)。


package main
import ( "fmt" "net")
func main() { _, err := net.Dial("tcp", "google.com:80") if err == nil { fmt.Println("Connection successful") } else { fmt.Println(err) }}
复制代码


为了不一个一个地测试每个端口,我们将添加一个简单的循环来简化整个测试过程。


package main
import ( "fmt" "net")
func main() { for port := 80; port < 100; port++ { conn, err := net.Dial("tcp", fmt.Sprintf("google.com:%d", port)) if err == nil { conn.Close() fmt.Println("Connection successful") } else { fmt.Println(err) } }}
复制代码


这种处理方式有个很大的问题,极度的慢。我们可以通过两个操作来处理一下:并行的执行及为每个连接添加超时控制。


我们来看下如何实现并行。第一步先把扫描功能拆分为一个独立函数。这样会使我们的代码看起来清晰。


func isOpen(host string, port int) bool {  time.Sleep(time.Millisecond * 1)  conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port))  if err == nil {     _ = conn.Close()     return true  }
return false}
复制代码


我们会引入一个新的方法 WaitGroup ,详细用法信息可以参考标准库文档。在主函数中,我们可以拆分为协程去执行,然后等待执行结束。


func main() {  ports := []int{}
wg := &sync.WaitGroup{} for port := 1; port < 100; port++ { wg.Add(1) go func() { opened := isOpen("google.com", port) if opened { ports = append(ports, port) } wg.Done() }() }
wg.Wait() fmt.Printf("opened ports: %v\n", ports)}
复制代码


我们的代码已经执行的很快了,但是由于超时的原因,我们需要等待很久才能收到返回的错误信息。我们可以假设如果我们 200 毫秒内没有收到服务器的回应,就不再继续等待。


func isOpen(host string, port int, timeout time.Duration) bool {  time.Sleep(time.Millisecond * 1)  conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), timeout)  if err == nil {    _ = conn.Close()    return true  }
return false}
func main() { ports := []int{}
wg := &sync.WaitGroup{} timeout := time.Millisecond * 200 for port := 1; port < 100; port++ { wg.Add(1) go func(p int) { opened := isOpen("google.com", p, timeout) if opened { ports = append(ports, p) } wg.Done() }(port) }
wg.Wait() fmt.Printf("opened ports: %v\n", ports)}
复制代码


至此,我们就得到了一个简单的端口扫描器。但有些不好的是,不能很方便的修改域名地址以及端口号范围,我们必须要重新编译代码才可以。Go 还有一个很不错的包叫做 flag 。


flag 包可以帮助我们编写命令行程序。我们可以配置每个字符串或数字。我们为主机名及要测试的端口范围和连接超时添加参数。


func main() {  hostname := flag.String("hostname", "", "hostname to test")  startPort := flag.Int("start-port", 80, "the port on which the scanning starts")  endPort := flag.Int("end-port", 100, "the port from which the scanning ends")  timeout := flag.Duration("timeout", time.Millisecond * 200, "timeout")  flag.Parse()
ports := []int{}
wg := &sync.WaitGroup{} for port := *startPort; port <= *endPort; port++ { wg.Add(1) go func(p int) { opened := isOpen(*hostname, p, *timeout) if opened { ports = append(ports, p) } wg.Done() }(port) }
wg.Wait() fmt.Printf("opened ports: %v\n", ports)}
复制代码


如果我们想要显示如何使用,我们可以添加一个 -h 参数,来显示使用说明。整个项目不到 50 行的代码,我们使用到了并行、flag 及 net 包。


唯一的问题就是,现在这个程序会有竞争条件。在只扫描少数端口时,速度比较慢,可能不会出现,但确实存在这个问题。所以我们需要使用 mutex 来修复它。


wg := &sync.WaitGroup{}mutex := &sync.Mutex{}for port := *startPort; port <= *endPort; port++ {  wg.Add(1)  go func(p int) {    opened := isOpen(*hostname, p, *timeout)    if opened {      mutex.Lock()      ports = append(ports, p)      mutex.Unlock()    }    wg.Done()  }(port)}
复制代码


我们本次只是简单的实现端口扫描的功能。如果大家喜欢编写这种工具,可以加入自己的理解或特性。参照 nmap 等著名扫描器的实现思路,用 Go 来打造自己的扫描器,从而加深对网络编程的理解。

总结

以上就是本次分享的内容~


如果有什么改进建议,也可以在我们评论区留言,供大家参考学习。


本文转载自公众号 360 云计算(ID:hulktalk)。


原文链接:


https://mp.weixin.qq.com/s/OhS_RQZojJbkenOSS_tEng


2019-11-14 13:582050

评论

发布
暂无评论
发现更多内容

Week 5命题作业

balsamspear

极客大学架构师训练营

【Knative系列】一文读懂 Knative Serving扩缩容的原理

公众号:云原生Serverless

Serverless knative autoscaler kantive

手动造轮子——基于.NetCore的RPC框架DotNetCoreRpc

yi念之间

RPC ASP.NET Core

搭载设计师级独显英特尔Xe MAX,非凡S3x体验全能创作

E科讯

甲方日常 45

句子

工作 随笔杂谈 日常

天源迪科受邀出席“第四届央企电商化采购发展高峰论坛"

DT极客

央行数字货币为人民币国际化之路提供推动力

CECBC

数字货币

「混合云」会是云计算的下一个战场吗?

ToB行业头条

阿里云

架构师第一期作业(第 7 周)

Cheer

课程作业

Dubbo-go Server端开启服务过程

apache/dubbo-go

dubbo dubbo-go dubbogo

阿里云视频云技术专家 LVS 演讲全文:《“云端一体”的智能媒体生产制作演进之路》

阿里云视频云

媒体 音视频

架构师训练营第一期 - week7

习习

Week 5学习总结

balsamspear

极客大学架构师训练营

区块链+能源 大放异彩

CECBC

区块链 能源

darknet A版安装

Dreamer

直播预告 | 应用加固防破解,4.1折就够了

蚂蚁集团移动开发平台 mPaaS

安全攻防 App风险 mPaaS

还在为算法烦恼?那你应该还没看过这份Git上70k标星的笔记

Java架构师迁哥

书写高质量SQL的30条建议

诸葛小猿

MySQL SQL优化

深入理解Java虚拟机第三版,通俗易懂,大牛带你轻松搞懂JVM性能调优

Java架构之路

Java 程序员 架构 面试 编程语言

配置企业管理系统,什么样的工作流才有用

雯雯写代码

工作流 企业管理系统

Android 一行代码接入 扫码 生成码

Java android kotlin zxing camera

手动造轮子——为Ocelot集成Nacos注册中心

yi念之间

nacos ASP.NET Core Ocelot

在深夜加油站遇见哈利波特

脑极体

high-performance-tidb-challenge 记录

程序员老王

响应式编程简介之:Reactor

程序那些事

响应式编程 reactor Reactive 程序那些事 响应式系统

英特尔首批独显笔记本亮相,非凡S3x纵享轻薄新体验

E科讯

银行数字化转型:需建立起以体验为核心、数据为基础、技术为驱动的架构体系

CECBC

银行 数字经济

DDIA 读书笔记(5)数据分区方案

莫黎

读书笔记

面试大厂被算法难倒惨遭滑铁卢?这份字节内部大佬整理的《数据结构与算法》学习笔记你一定要看看!

Java架构之路

Java 程序员 架构 面试 编程语言

Week 7 命题作业

阿泰

【性能优化】纳尼?内存又溢出了?!是时候总结一波了!!

冰河

性能优化 内存泄露 高并发 高性能 内存溢出

Go实现简单TCP扫描器_文化 & 方法_360云计算_InfoQ精选文章