写点什么

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

评论

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

Redis数据结构

苏格拉格拉

redis 缓存 Redis 数据结构

并发编程中的锁、条件变量和信号量

C++后台开发

Go 并发编程 linux开发 C++开发

Redis核心技术

苏格拉格拉

redis 架构 持久化 部署 集群

oracle中计算两个日期的相差天数、月数、年数等等

默默的成长

oracle 前端 11月月更

1年Java经验,信心满满出去面试,被问麻了...

Java永远的神

spring 程序员 后端 JVM Java 面试

阿里云机器学习平台 PAI宣布集成国产深度学习框架 OneFlow

阿里云大数据AI技术

机器学习 阿里云 oneflow

双11就要到了,是时候给你的电脑来点硬货了!

淋雨

数据恢复 OCR 滤镜 录屏 磨皮

赋能信息技术应用创新,需要怎样的可持续性业务架构?

通明湖

负载均衡

FOTSL:端到端的文本检测与识别方法的原理方法与优势

合合技术团队

人工智能 场景 端口 文本检测 文本识别

产品网站的FAQ页面该如何编辑?

Baklib

产品 FAQ

Baklib|如何搭建在线帮助中心站点?

Baklib

最新的国内低代码开发平台排名,你知道几个?

优秀

低代码 低代码开发平台 低代码平台

微服务先等等,我去刷个“虚拟背景”的副本

为自己带盐

虚拟背景 11月月更 trtc

概述DDoS分类

穿过生命散发芬芳

DDoS 11月月更

Wallys/QCN9074 WiFi 6E Card OpenWRT, IPQ6010, IPQ6018,802.11ax,industrial m.2 card/QUECTEL RM500Q-GL

wallys-wifi6

IPQ6010 ipq6018 QCN9074

阿里进入“全面云原生深度用云”阶段 PaaS支出占用云总成本43%

阿里技术

云计算 云原生 云栖大会

uniapp引入 iconfont

源字节1号

微信小程序 软件开发 前端开发 后端开发

HTML学习笔记(一)

lxmoe

html 前端 学习笔记 11月月更

如何通过Java将PDF转为Excel

Geek_249eec

Excel PDF java;

Oracle表空间设计基本原则

默默的成长

oracle 前端 11月月更

网易云信智码超清转码技术实践

网易云信

音视频开发

RocketMQ核心技术

苏格拉格拉

RocketMQ 消息队列 消息中间件 微服务框架

介绍:什么是智能合约dapp系统开发一站式服务

W13902449729

Docker PHP 入门实践 (三)

Felix

php Docker thinkphp 11月月更

Dubbo核心技术

苏格拉格拉

分布式 微服务 dubbo RPC 集群

什么是 NFT 蓝筹项目

NFT Research

区块链 NFT

云科通明湖:金融业务可持续性能力建设,少不了这块“拼图”!

通明湖

负载均衡

智采云火了的背后,是企业降本增效的刚需

ToB行业头条

Oracle 开发规范(一)

默默的成长

oracle 前端 11月月更

云栖大会开源重磅升级!PolarDB-X v2.2: 企业级和国产化适配

阿里云数据库开源

阿里云 polarDB 云栖大会 PolarDB-X 阿里云数据库

游戏链改NFT系统开发Web3技术

薇電13242772558

web3

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