写点什么

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:582225

评论

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

正则表达式

Jason199

正则表达式 js 7月月更

CleanMyMac X试用版Mac清理工具

茶色酒

CleanMyMac CleanMyMacX CleanMyMac X

非Vuex实现的登录状态判断封装

猪痞恶霸

Vue 前端 7月月更

云原生应用开发之 gRPC 入门

宇宙之一粟

Go gRPC 云原生 Go 语言 7月月更

CorelDRAW2022下载安装电脑系统要求技术规格

茶色酒

cdr2022

接口测试要测试什么?

Xd

Java 后端 接口测试

Flutter 3.0框架下的小程序运行

FN0

flutter 前端框架 小程序容器

CleanMyMac X2022全新版功能介绍

茶色酒

CleanMyMac CleanMyMac X

Android 应用界面风格与主题

芝麻粒儿

android 7月月更 手机开发

spark调优(四):瘦身任务主体

怀瑾握瑜的嘉与嘉

spark 7月月更

【愚公系列】2022年7月 Go教学课程 006-自动推导类型和输入输出

愚公搬代码

7月月更

ORACLE进阶(七)存储过程详解

No Silver Bullet

oracle 存储过程 7月月更

Qt|使用QWebEngineView加载HTML使用及问题

中国好公民st

qt 7月月更

Service Mesh的基本模式

阿泽🧸

Service Mesh 7月月更

iOS中类的本质及其存储

NewBoy

前端 移动端 iOS 知识体系 7月月更

Qt实现音频播放

小肉球

qt 7月月更

5G NR 系统消息

柒号华仔

5G 7月月更

java编程思想

乌龟哥哥

7月月更

图解网络:揭开TCP四次挥手背后的原理,结合男女朋友分手的例子,通俗易懂

wljslmz

TCP 网络协议 网络技术 7月月更 TCP四次挥手

【SolidWorks】修改工程图格式

大头博士先生

SlideWorks

什么是数据资产?为什么背后蕴藏45万亿这么大的市场?

雨果

数据资产 数字经济

接口测试进阶接口脚本使用—apipost(预/后执行脚本)

Xd

Java 数据库 接口测试工具

攻防演练中沙盘推演的4个阶段

穿过生命散发芬芳

7月月更 沙盘推演

【深度学习】AI一键换天

逝缘~

人工智能 7月月更

测试部门的职责定位

BY林子

软件测试 敏捷测试 测试转型 测试部门职责 测试定位

牛客基础语法必刷100题之基本类型

京与旧铺

7月月更

Ubuntu安装PyCharm

IT蜗壳-Tango

7月月更

从Starfish OS持续对SFO的通缩消耗,长远看SFO的价值

股市老人

Python|读写文件

AXYZdong

Python 7月月更

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