diff --git a/agent/app/api/v2/process.go b/agent/app/api/v2/process.go index d7190b717b7a..b84fb45819bd 100644 --- a/agent/app/api/v2/process.go +++ b/agent/app/api/v2/process.go @@ -57,3 +57,18 @@ func (b *BaseApi) GetProcessInfoByPID(c *gin.Context) { } helper.SuccessWithData(c, data) } + +// @Tags Process +// @Summary Get Listening Process +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /process/listening [post] +func (b *BaseApi) GetListeningProcess(c *gin.Context) { + procs, err := processService.GetListeningProcess(c) + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, procs) +} diff --git a/agent/app/service/firewall.go b/agent/app/service/firewall.go index 0e55b82800d8..f54c3eef2bc7 100644 --- a/agent/app/service/firewall.go +++ b/agent/app/service/firewall.go @@ -720,16 +720,9 @@ func checkPortUsed(ports, proto string, apps []portOfApp) string { for _, app := range apps { if app.HttpPort == ports || app.HttpsPort == ports { - return fmt.Sprintf("(%s)", app.AppName) + return app.AppName } } - port, err := strconv.Atoi(ports) - if err != nil { - global.LOG.Errorf(" convert string %v to int failed, err: %v", port, err) - return "" - } - if common.ScanPortWithProto(port, proto) { - return "inUsed" - } + return "" } diff --git a/agent/app/service/process.go b/agent/app/service/process.go index 3bcefe9f1291..7b2686708866 100644 --- a/agent/app/service/process.go +++ b/agent/app/service/process.go @@ -2,15 +2,19 @@ package service import ( "bufio" + "context" "fmt" - "github.com/1Panel-dev/1Panel/agent/app/dto/request" - "github.com/1Panel-dev/1Panel/agent/utils/common" - "github.com/1Panel-dev/1Panel/agent/utils/websocket" - "github.com/shirou/gopsutil/v4/process" "os" "strconv" "strings" + "syscall" "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/websocket" + "github.com/shirou/gopsutil/v4/net" + "github.com/shirou/gopsutil/v4/process" ) type ProcessService struct{} @@ -18,6 +22,7 @@ type ProcessService struct{} type IProcessService interface { StopProcess(req request.ProcessReq) error GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error) + GetListeningProcess(c context.Context) ([]ListeningProcess, error) } func NewIProcessService() IProcessService { @@ -35,6 +40,55 @@ func (ps *ProcessService) StopProcess(req request.ProcessReq) error { return nil } +type ListeningProcess struct { + PID int32 + Port map[uint32]struct{} + Protocol uint32 + Name string +} + +func (ps *ProcessService) GetListeningProcess(c context.Context) ([]ListeningProcess, error) { + conn, err := net.ConnectionsMaxWithContext(c, "inet", 32768) + if err != nil { + return nil, err + } + procCache := make(map[int32]ListeningProcess, 64) + + for _, conn := range conn { + if conn.Pid == 0 { + continue + } + + if (conn.Status == "LISTEN" && conn.Type == syscall.SOCK_STREAM) || (conn.Type == syscall.SOCK_DGRAM && conn.Raddr.Port == 0) { + if _, exists := procCache[conn.Pid]; !exists { + proc, err := process.NewProcess(conn.Pid) + if err != nil { + continue + } + procData := ListeningProcess{ + PID: conn.Pid, + } + procData.Name, _ = proc.Name() + procData.Port = make(map[uint32]struct{}) + procData.Port[conn.Laddr.Port] = struct{}{} + procData.Protocol = conn.Type + procCache[conn.Pid] = procData + } else { + p := procCache[conn.Pid] + p.Port[conn.Laddr.Port] = struct{}{} + procCache[conn.Pid] = p + } + } + } + + procs := make([]ListeningProcess, 0, len(procCache)) + for _, proc := range procCache { + procs = append(procs, proc) + } + + return procs, nil +} + func (ps *ProcessService) GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error) { p, err := process.NewProcess(pid) if err != nil { diff --git a/agent/router/ro_process.go b/agent/router/ro_process.go index 32e15b91dd7e..685de151f2ce 100644 --- a/agent/router/ro_process.go +++ b/agent/router/ro_process.go @@ -14,6 +14,7 @@ func (f *ProcessRouter) InitRouter(Router *gin.RouterGroup) { { processRouter.GET("/ws", baseApi.ProcessWs) processRouter.POST("/stop", baseApi.StopProcess) + processRouter.POST("/listening", baseApi.GetListeningProcess) processRouter.GET("/:pid", baseApi.GetProcessInfoByPID) } } diff --git a/frontend/src/api/interface/process.ts b/frontend/src/api/interface/process.ts index a20093ea83b0..cc1a22c40eb5 100644 --- a/frontend/src/api/interface/process.ts +++ b/frontend/src/api/interface/process.ts @@ -55,4 +55,11 @@ export namespace Process { path: string; fd: number; } + + export interface ListeningProcess { + PID: number; + Port: { [key: string]: {} }; + Protocol: number; + Name: string; + } } diff --git a/frontend/src/api/modules/process.ts b/frontend/src/api/modules/process.ts index 28545abec5b6..16ac1fbba1f3 100644 --- a/frontend/src/api/modules/process.ts +++ b/frontend/src/api/modules/process.ts @@ -8,3 +8,7 @@ export const stopProcess = (req: Process.StopReq) => { export const getProcessByID = (pid: number) => { return http.get(`/process/${pid}`); }; + +export const getListeningProcess = () => { + return http.post(`/process/listening`); +}; diff --git a/frontend/src/views/host/firewall/port/index.vue b/frontend/src/views/host/firewall/port/index.vue index 5ae29f50487c..8b77404b4590 100644 --- a/frontend/src/views/host/firewall/port/index.vue +++ b/frontend/src/views/host/firewall/port/index.vue @@ -88,28 +88,20 @@ @@ -163,6 +155,7 @@ + @@ -171,12 +164,16 @@ import FireRouter from '@/views/host/firewall/index.vue'; import OperateDialog from '@/views/host/firewall/port/operate/index.vue'; import ImportDialog from '@/views/host/firewall/port/import/index.vue'; import FireStatus from '@/views/host/firewall/status/index.vue'; +import ProcessDetail from '@/views/host/process/process/detail/index.vue'; import { onMounted, reactive, ref } from 'vue'; import { batchOperateRule, searchFireRule, updateFirewallDescription, updatePortRule } from '@/api/modules/host'; +import { getListeningProcess } from '@/api/modules/process'; import { Host } from '@/api/interface/host'; +import { Process } from '@/api/interface/process'; import i18n from '@/lang'; import { MsgSuccess } from '@/utils/message'; import { ElMessageBox } from 'element-plus'; +import { Expand } from '@element-plus/icons-vue'; import { routerToName } from '@/utils/router'; import { downloadWithContent, getCurrentDateFormatted } from '@/utils/util'; @@ -195,6 +192,9 @@ const fireStatusRef = ref(); const opRef = ref(); const dialogImportRef = ref(); +const processDetailRef = ref(); + +const listeningProcesses = ref([]); const data = ref(); const paginationConfig = reactive({ @@ -204,6 +204,46 @@ const paginationConfig = reactive({ total: 0, }); +const extractPortsFromObject = (portObj: { [key: string]: {} }): number[] => { + return Object.keys(portObj) + .map((portStr) => parseInt(portStr)) + .filter((port) => !isNaN(port)); +}; + +const isSinglePort = (portStr: string): boolean => { + return portStr.indexOf('-') === -1 && portStr.indexOf(':') === -1 && portStr.indexOf(',') === -1; +}; + +const loadListeningProcesses = async () => { + try { + const res = await getListeningProcess(); + listeningProcesses.value = res.data || []; + + for (const item of data.value) { + if (!item.usedStatus && isSinglePort(item.port)) { + const portNum = parseInt(item.port.trim()); + if (!isNaN(portNum)) { + const protocolNum = + item.protocol.toLowerCase() === 'tcp' ? 1 : item.protocol.toLowerCase() === 'udp' ? 2 : 0; + + for (const proc of listeningProcesses.value) { + if (proc.Protocol === protocolNum) { + const procPorts = extractPortsFromObject(proc.Port); + if (procPorts.includes(portNum)) { + item.usedStatus = proc.Name; + item.processInfo = proc; + break; + } + } + } + } + } + } + } catch (error) { + console.error('Failed to load listening processes:', error); + } +}; + const search = async () => { if (!isActive.value) { loading.value = false; @@ -221,12 +261,12 @@ const search = async () => { }; loading.value = true; await searchFireRule(params) - .then((res) => { + .then(async (res) => { loading.value = false; data.value = res.data.items || []; - for (const item of data.value) { - item.usedPorts = item.usedStatus ? item.usedStatus.split(',') : []; - } + + await loadListeningProcesses(); + paginationConfig.total = res.data.total; }) .catch(() => { @@ -381,6 +421,10 @@ const onExport = () => { }); }; +const showProcessDetail = (pid: number) => { + processDetailRef.value?.acceptParams(pid); +}; + const buttons = [ { label: i18n.global.t('commons.button.edit'),