port_scan.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package system
  2. import (
  3. "fmt"
  4. "net"
  5. "os/exec"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/gin-gonic/gin"
  10. "github.com/uozi-tech/cosy/logger"
  11. )
  12. type PortScanRequest struct {
  13. StartPort int `json:"start_port" binding:"required,min=1,max=65535"`
  14. EndPort int `json:"end_port" binding:"required,min=1,max=65535"`
  15. Page int `json:"page" binding:"required,min=1"`
  16. PageSize int `json:"page_size" binding:"required,min=1,max=1000"`
  17. }
  18. type PortInfo struct {
  19. Port int `json:"port"`
  20. Status string `json:"status"`
  21. Process string `json:"process"`
  22. }
  23. type PortScanResponse struct {
  24. Data []PortInfo `json:"data"`
  25. Total int `json:"total"`
  26. Page int `json:"page"`
  27. PageSize int `json:"page_size"`
  28. }
  29. func PortScan(c *gin.Context) {
  30. var req PortScanRequest
  31. if err := c.ShouldBindJSON(&req); err != nil {
  32. c.JSON(400, gin.H{"message": err.Error()})
  33. return
  34. }
  35. if req.StartPort > req.EndPort {
  36. c.JSON(400, gin.H{"message": "Start port must be less than or equal to end port"})
  37. return
  38. }
  39. // Calculate pagination
  40. totalPorts := req.EndPort - req.StartPort + 1
  41. startIndex := (req.Page - 1) * req.PageSize
  42. endIndex := startIndex + req.PageSize
  43. if startIndex >= totalPorts {
  44. c.JSON(200, PortScanResponse{
  45. Data: []PortInfo{},
  46. Total: totalPorts,
  47. Page: req.Page,
  48. PageSize: req.PageSize,
  49. })
  50. return
  51. }
  52. if endIndex > totalPorts {
  53. endIndex = totalPorts
  54. }
  55. // Calculate actual port range for this page
  56. actualStartPort := req.StartPort + startIndex
  57. actualEndPort := req.StartPort + endIndex - 1
  58. var ports []PortInfo
  59. // Get listening ports info
  60. listeningPorts := getListeningPorts()
  61. // Scan ports in the current page range
  62. for port := actualStartPort; port <= actualEndPort; port++ {
  63. portInfo := PortInfo{
  64. Port: port,
  65. Status: "closed",
  66. Process: "",
  67. }
  68. // Check if port is listening
  69. if processInfo, exists := listeningPorts[port]; exists {
  70. portInfo.Status = "listening"
  71. portInfo.Process = processInfo
  72. } else {
  73. // Quick check if port is open but not in listening list
  74. if isPortOpen(port) {
  75. portInfo.Status = "open"
  76. }
  77. }
  78. ports = append(ports, portInfo)
  79. }
  80. c.JSON(200, PortScanResponse{
  81. Data: ports,
  82. Total: totalPorts,
  83. Page: req.Page,
  84. PageSize: req.PageSize,
  85. })
  86. }
  87. func isPortOpen(port int) bool {
  88. timeout := time.Millisecond * 100
  89. conn, err := net.DialTimeout("tcp", fmt.Sprintf("localhost:%d", port), timeout)
  90. if err != nil {
  91. return false
  92. }
  93. defer conn.Close()
  94. return true
  95. }
  96. func getListeningPorts() map[int]string {
  97. ports := make(map[int]string)
  98. // Try netstat first
  99. if cmd := exec.Command("netstat", "-tlnp"); cmd.Err == nil {
  100. if output, err := cmd.Output(); err == nil {
  101. lines := strings.Split(string(output), "\n")
  102. for _, line := range lines {
  103. if strings.Contains(line, "LISTEN") {
  104. fields := strings.Fields(line)
  105. if len(fields) >= 4 {
  106. address := fields[3]
  107. process := ""
  108. if len(fields) >= 7 {
  109. process = fields[6]
  110. }
  111. // Extract port from address (format: 0.0.0.0:port or :::port)
  112. if colonIndex := strings.LastIndex(address, ":"); colonIndex != -1 {
  113. portStr := address[colonIndex+1:]
  114. if port, err := strconv.Atoi(portStr); err == nil {
  115. ports[port] = process
  116. }
  117. }
  118. }
  119. }
  120. }
  121. return ports
  122. }
  123. }
  124. // Fallback to ss command
  125. if cmd := exec.Command("ss", "-tlnp"); cmd.Err == nil {
  126. if output, err := cmd.Output(); err == nil {
  127. lines := strings.Split(string(output), "\n")
  128. for _, line := range lines {
  129. if strings.Contains(line, "LISTEN") {
  130. fields := strings.Fields(line)
  131. if len(fields) >= 4 {
  132. address := fields[3]
  133. process := ""
  134. if len(fields) >= 6 {
  135. process = fields[5]
  136. }
  137. // Extract port from address
  138. if colonIndex := strings.LastIndex(address, ":"); colonIndex != -1 {
  139. portStr := address[colonIndex+1:]
  140. if port, err := strconv.Atoi(portStr); err == nil {
  141. ports[port] = process
  142. }
  143. }
  144. }
  145. }
  146. }
  147. }
  148. }
  149. logger.Debug("Found listening ports: %v", ports)
  150. return ports
  151. }