245 lines
5.6 KiB
Go
245 lines
5.6 KiB
Go
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("无法找到可用端口")
|
||
}
|