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
}