您的当前位置:首页正文

基于Go语言的端口扫描工具设计与实现(完整代码)

2024-11-29 来源:个人技术集锦

main 函数:

解析端口信息并转换为 PortInfo 结构体的切片。
调用 DiscoverAssets 函数进行扫描。
输出扫描结果。

incrementIP 函数:

将 IP 地址递增,以便遍历子网中的所有 IP 地址。

parsePortInfo 函数:

将简化的端口信息字符串(例如 "21,22,80,U:137,U:161,443,445,U:1900,3306,3389,U:5353,8080")解析为 PortInfo 结构体的切片。
支持 TCP 和 UDP 协议。

ScanPort 函数:

根据协议(TCP 或 UDP)扫描指定 IP 地址上的端口。
对于 UDP 端口,尝试读取数据以确定端口是否开放。
使用 net.DialTimeout 函数进行连接,并设置超时时间。

netErrorTimeout 函数:

检查错误是否为超时错误。

DiscoverAssets 函数:

扫描子网中的所有 IP 地址和端口。
使用并发扫描,通过 semaphore 控制并发数量。
将扫描结果发送到 results 通道,并通过 assets 切片收集结果。

具体操作

并发控制:

使用 semaphore 控制并发的扫描任务数。maxConcurrency 控制同时进行的扫描数量。

结果收集:

使用 results 通道收集扫描结果。
启动一个 goroutine 来从 results 通道中读取结果并将其添加到 assets 切片中。

扫描流程:

遍历子网中的每个 IP 地址。
对于每个 IP 地址,遍历所有的端口信息。
对每个端口,启动一个 goroutine 进行扫描。
扫描完成后,将结果发送到 results 通道。

等待和关闭:

使用 sync.WaitGroup 等待所有扫描任务完成。
关闭 results 通道以指示结果收集完成。
package main

import (
	"fmt"
	"github.com/labstack/gommon/log"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"
)
// 扫描端口的结构体
type PortInfo struct {
	Port     int
	Protocol string
}
type ScanningAsset struct {
	IP      string          `gorm:"type:varchar(200)" json:"ip"`
	Port    int             `json:"port"`
	Status  string          `gorm:"type:varchar(500)" json:"status"`
}
func main() {
	subnet := "192.168.2.0/24"
	simplifyPort := "21,22,80,U:137,U:161,443,445,U:1900,3306,3389,U:5353,8080"
	maxConcurrency := 200
	portInfoList := parsePortInfo(simplifyPort)
	scannedAssets := DiscoverAssets(subnet, portInfoList, maxConcurrency)
	for _, asset := range scannedAssets {
		fmt.Println(asset)
	}
}

func incrementIP(ip net.IP) {
	for j := len(ip) - 1; j >= 0; j-- {
		ip[j]++
		if ip[j] > 0 {
			break
		}
	}
}

func parsePortInfo(portStr string) []PortInfo {
	var portInfoList []PortInfo
	segments := strings.Split(portStr, ",")
	for _, segment := range segments {
		if segment == "" {
			continue
		}
		parts := strings.Split(segment, ":")
		protocol := "tcp" // 默认协议
		port := parts[0]
		if len(parts) > 1 {
			protocol = "udp"
			port = parts[1]
		}
		portNumber, err := strconv.Atoi(port)
		if err != nil {
			log.Debug(err.Error())
			continue
		}
		portInfoList = append(portInfoList, PortInfo{Port: portNumber, Protocol: protocol})
	}
	return portInfoList
}

func ScanPort(ip string, portInfo PortInfo) (string, error) {
	address := fmt.Sprintf("%s:%d", ip, portInfo.Port)
	log.Debug(address)
	conn, err := net.DialTimeout(portInfo.Protocol, address, 1*time.Second)
	if err != nil {
		return "closed", err
	}
	if portInfo.Protocol == "udp" {
		conn.SetReadDeadline(time.Now().Add(1 * time.Second))
		_, err := conn.Read(make([]byte, 1))
		if err != nil && !netErrorTimeout(err) {
			return "closed", err
		}
	}
	conn.Close()
	return "open", nil
}

func netErrorTimeout(err error) bool {
	if netErr, ok := err.(net.Error); ok {
		return netErr.Timeout()
	}
	return false
}

func DiscoverAssets(subnet string, portInfoList []PortInfo, maxConcurrency int) []ScanningAsset {
	var assets []ScanningAsset
	var wg sync.WaitGroup
	var mu sync.Mutex
	semaphore := make(chan struct{}, maxConcurrency)
	results := make(chan ScanningAsset)

	go func() {
		for result := range results {
			mu.Lock()
			assets = append(assets, result)
			mu.Unlock()
		}
	}()

	ip, ipNet, err := net.ParseCIDR(subnet)
	if err != nil {
		fmt.Println("Invalid subnet:", err)
		return assets
	}

	for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); incrementIP(ip) {
		for _, portInfo := range portInfoList {
			wg.Add(1)
			semaphore <- struct{}{}
			go func(ip string, portinfo PortInfo) {
				defer wg.Done()
				status, _ := ScanPort(ip, portinfo)
				if status == "open" {
					results <- ScanningAsset{
						IP:      ip,
						Port:    portinfo.Port,
						Status:  status,
					}
				}
				<-semaphore
			}(ip.String(), portInfo)
		}
	}

	wg.Wait()
	close(results)
	return assets
}
显示全文