Go语言实现IP段端口扫描工具教程
本教程分享一个基于Go语言开发的简易端口扫描器,旨在演示如何利用Golang的高并发特性快速检测指定IP段的开放端口。文章涵盖核心算法解析、多线程并发控制及数据持久化输出方法。
工具概述
该程序是一个命令行工具,主要功能包括生成连续的IP地址序列、对每个IP的目标端口建立TCP连接(即端口扫描),并根据扫描结果将开放的IP列表写入以当前日期命名的文本文件中。由于使用了Go语言的协程(goroutine)机制,它能够显著提升大规模IP段的扫描速度。
核心功能与逻辑
1. IP地址数值化处理
为了高效生成连续的IP段,程序首先需要将点分十进制的IP地址转换为整数,并反之亦然。这通常通过位运算完成:
ip2num:将IP字符串按“.”分割为四个整数,并通过左移和按位或操作合并为一个32位整数。num2ip:将整数通过右移和按位与操作还原为四个八位字节,再拼接回标准的IP格式。
2. 并发扫描模型
程序不采用单线程轮询,而是启用了多个并发工作协程。扫描逻辑分为两步:
- 入队:根据起始和结束IP生成所有目标IP,压入全局队列中。
- 处理:启动多个协程,从队列中取出IP并与目标端口进行TCP握手。如果握手成功,则认为端口开放。
3. 结果存储
扫描成功后,程序会将通过的IP地址追加写入到本地文件。文件名遵循 日期_端口_ok.txt 的格式,例如 2023-10-27_80_ok.txt。
完整代码解析
以下是该工具的完整源代码。请注意,原代码中存在部分变量命名不规范和并发竞争条件(Race Condition)的隐患,此处保持原汁原味展示其逻辑结构,建议在实际生产环境中优化锁机制。
package main
import (
"container/list"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
)
// ip2num: 将IP地址转换为整型值
func ip2num(ip string) int {
canSplit := func(c rune) bool { return c == '.' }
lisit := strings.FieldsFunc(ip, canSplit)
ip1_str_int, _ := strconv.Atoi(lisit[0])
ip2_str_int, _ := strconv.Atoi(lisit[1])
ip3_str_int, _ := strconv.Atoi(lisit[2])
ip4_str_int, _ := strconv.Atoi(lisit[3])
return ip1_str_int<<24 | ip2_str_int<<16 | ip3_str_int<<8 | ip4_str_int
}
// num2ip: 将整型值转换为IP地址
func num2ip(num int) string {
ip1_int := (num & 0xff000000) >> 24
ip2_int := (num & 0x00ff0000) >> 16
ip3_int := (num & 0x0000ff00) >> 8
ip4_int := num & 0x000000ff
data := fmt.Sprintf("%d.%d.%d.%d", ip1_int, ip2_int, ip3_int, ip4_int)
return data
}
var l = list.New()
// gen_ip: 生成IP段并入队
func gen_ip(Aip1 int, Aip2 int) {
index := Aip1
for index < Aip2 {
ip_data := num2ip(index)
l.PushBack(ip_data)
index++
}
}
// socket_ip: 尝试TCP连接判断端口状态
func socket_ip(host string, port string) bool {
remote := host + ":" + port
tcpAddr, _ := net.ResolveTCPAddr("tcp4", remote)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
fmt.Printf("no==%s:%s\r\n", host, port)
return false
}
defer conn.Close()
fmt.Printf("ok==%s:%s\r\n", host, port)
return true
}
// text_add: 向文件中追加数据
func text_add(name string, data string) {
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0x644)
if err != nil {
panic(err)
}
defer f.Close()
_, err = f.WriteString(data)
_, err = f.WriteString("\r\n")
if err != nil {
panic(err)
}
}
// for_ip: 协程入口,持续出队扫描
func for_ip(port string) {
now := time.Now()
year, mon, day := now.UTC().Date()
file_name := fmt.Sprintf("%d-%d-%d_%s", year, mon, day, port)
for {
if l.Len() <= 0 {
break
}
i1 := l.Front()
l.Remove(i1)
IP, _ := i1.Value.(string)
if socket_ip(IP, port) {
text_add(file_name+"_ok.txt", IP)
}
time.Sleep(time.Millisecond * 500)
}
}
func main() {
argsLen := len(os.Args)
if argsLen != 4 {
fmt.Println("用法: main.go 起始IP 结束IP 端口号")
fmt.Println("示例: main.go 58.215.20.30 58.215.201.30 80")
} else {
// 生成IP队列
gen_ip(ip2num(os.Args[1]), ip2num(os.Args[2]))
// 启动200个并发协程
for index := 0; index < 200; index++ {
go for_ip(os.Args[3])
}
// 主进程休眠防止退出
for {
time.Sleep(1 * time.Second)
}
}
}
使用方法
编译并运行该程序时,需要通过命令行传递三个参数:起始IP、结束IP 和 扫描端口。程序将自动计算中间的IP数量并按顺序发起连接请求。
- 确保系统已安装 Go 环境。
- 保存上述代码至
main.go。 - 执行编译命令:
go build main.go - 运行工具:
./main 192.168.1.1 192.168.1.10 22
完成后,在当前目录下查找类似 202X-X-X_22_ok.txt 的文件,即可看到扫描成功的IP列表。
以上就是Go语言实现IP段端口扫描工具的详细内容,更多关于Golang网络编程与安全审计的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













