Files
port-manager/internal/scanner/port_scanner.go

245 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package scanner
import (
"bufio"
"fmt"
"net"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
"github.com/miaomint/port-manager/pkg/types"
)
// PortScanner 端口扫描器
type PortScanner struct {
includeListening bool
includeEstablished bool
}
// NewPortScanner 创建新的端口扫描器
func NewPortScanner(includeListening, includeEstablished bool) *PortScanner {
return &PortScanner{
includeListening: includeListening,
includeEstablished: includeEstablished,
}
}
// ScanPorts 扫描本地端口
func (ps *PortScanner) ScanPorts() (*types.ScanResult, error) {
var ports []types.PortInfo
// 获取TCP端口
tcpPorts, err := ps.scanTCPPorts()
if err != nil {
return nil, fmt.Errorf("扫描TCP端口失败: %v", err)
}
ports = append(ports, tcpPorts...)
// 获取UDP端口
udpPorts, err := ps.scanUDPPorts()
if err != nil {
return nil, fmt.Errorf("扫描UDP端口失败: %v", err)
}
ports = append(ports, udpPorts...)
return &types.ScanResult{
Ports: ports,
Timestamp: time.Now(),
TotalPorts: len(ports),
}, nil
}
// scanTCPPorts 扫描TCP端口使用netstat
func (ps *PortScanner) scanTCPPorts() ([]types.PortInfo, error) {
var args []string
args = append(args, "-an")
args = append(args, "-p", "tcp")
cmd := exec.Command("netstat", args...)
output, err := cmd.Output()
if err != nil {
return nil, err
}
return ps.parseNetstatOutput(string(output), "tcp")
}
// scanUDPPorts 扫描UDP端口
func (ps *PortScanner) scanUDPPorts() ([]types.PortInfo, error) {
var args []string
args = append(args, "-an")
args = append(args, "-p", "udp")
cmd := exec.Command("netstat", args...)
output, err := cmd.Output()
if err != nil {
return nil, err
}
return ps.parseNetstatOutput(string(output), "udp")
}
// parseNetstatOutput 解析netstat输出
func (ps *PortScanner) parseNetstatOutput(output, protocol string) ([]types.PortInfo, error) {
var ports []types.PortInfo
scanner := bufio.NewScanner(strings.NewReader(output))
// 跳过标题行
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "Local Address") {
break
}
}
// 解析每一行
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
port, err := ps.parseNetstatLineMacOS(line, protocol)
if err != nil {
continue // 跳过解析失败的行
}
if port != nil {
// 过滤状态
if ps.shouldIncludePort(port) {
ports = append(ports, *port)
}
}
}
return ports, nil
}
// parseNetstatLineMacOS 解析macOS netstat的单行输出
func (ps *PortScanner) parseNetstatLineMacOS(line, protocol string) (*types.PortInfo, error) {
fields := regexp.MustCompile(`\s+`).Split(line, -1)
if len(fields) < 4 {
return nil, fmt.Errorf("invalid line format")
}
// macOS netstat格式: Proto Recv-Q Send-Q Local-Address Foreign-Address (state)
localAddr := fields[3]
state := ""
if len(fields) >= 6 {
state = fields[5]
}
// 解析本地地址可能包含IPv6地址
var portStr string
if strings.Contains(localAddr, ".") {
// IPv4 格式: ip.port
parts := strings.Split(localAddr, ".")
if len(parts) >= 2 {
portStr = parts[len(parts)-1]
}
} else if strings.Contains(localAddr, ":") {
// IPv6 格式: [ip]:port 或 ip:port
if strings.Contains(localAddr, "]:") {
// [ip]:port
parts := strings.Split(localAddr, "]:")
if len(parts) == 2 {
portStr = parts[1]
}
} else {
// ip:port (简单情况)
parts := strings.Split(localAddr, ":")
if len(parts) >= 2 {
portStr = parts[len(parts)-1]
}
}
}
if portStr == "" {
return nil, fmt.Errorf("could not extract port from address: %s", localAddr)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port: %s", portStr)
}
return &types.PortInfo{
Port: port,
Protocol: protocol,
State: state,
LastSeen: time.Now(),
}, nil
}
// shouldIncludePort 判断是否应该包含此端口
func (ps *PortScanner) shouldIncludePort(port *types.PortInfo) bool {
if port.Protocol == "tcp" {
// macOS netstat 状态值可能包括: LISTEN, ESTABLISHED, SYN_SENT, etc.
if strings.Contains(port.State, "LISTEN") {
return ps.includeListening
}
if strings.Contains(port.State, "ESTABLISHED") {
return ps.includeEstablished
}
// 对于其他状态,如果设置了包含监听端口,也包含进来
return ps.includeListening
}
// UDP端口默认包含如果启用了监听端口
if port.Protocol == "udp" {
return ps.includeListening
}
return false
}
// ScanPortRange 扫描指定端口范围
func (ps *PortScanner) ScanPortRange(start, end int) ([]int, error) {
var openPorts []int
for port := start; port <= end; port++ {
// TCP扫描
conn, err := net.DialTimeout("tcp", fmt.Sprintf("localhost:%d", port), time.Millisecond*100)
if err == nil {
conn.Close()
openPorts = append(openPorts, port)
}
}
return openPorts, nil
}
// IsPortOpen 检查指定端口是否开放
func (ps *PortScanner) IsPortOpen(port int, protocol string) bool {
address := fmt.Sprintf("localhost:%d", port)
conn, err := net.DialTimeout(protocol, address, time.Millisecond*100)
if err != nil {
return false
}
conn.Close()
return true
}
// GenerateRandomPort 生成一个未被占用的随机端口
func (ps *PortScanner) GenerateRandomPort(minPort, maxPort int) (int, error) {
if minPort <= 0 {
minPort = 8000
}
if maxPort <= 0 || maxPort > 65535 {
maxPort = 65535
}
for attempts := 0; attempts < 100; attempts++ {
port := minPort + (int(time.Now().UnixNano()) % (maxPort - minPort + 1))
if !ps.IsPortOpen(port, "tcp") {
return port, nil
}
}
return 0, fmt.Errorf("无法找到可用端口")
}