Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions agent/app/api/v2/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
11 changes: 2 additions & 9 deletions agent/app/service/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
}
62 changes: 58 additions & 4 deletions agent/app/service/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@ 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{}

type IProcessService interface {
StopProcess(req request.ProcessReq) error
GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error)
GetListeningProcess(c context.Context) ([]ListeningProcess, error)
}

func NewIProcessService() IProcessService {
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions agent/router/ro_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
7 changes: 7 additions & 0 deletions frontend/src/api/interface/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@ export namespace Process {
path: string;
fd: number;
}

export interface ListeningProcess {
PID: number;
Port: { [key: string]: {} };
Protocol: number;
Name: string;
}
}
4 changes: 4 additions & 0 deletions frontend/src/api/modules/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ export const stopProcess = (req: Process.StopReq) => {
export const getProcessByID = (pid: number) => {
return http.get<Process.PsProcessData>(`/process/${pid}`);
};

export const getListeningProcess = () => {
return http.post<Process.ListeningProcess[]>(`/process/listening`);
};
92 changes: 68 additions & 24 deletions frontend/src/views/host/firewall/port/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -88,28 +88,20 @@
<el-table-column :label="$t('commons.table.port')" :min-width="70" prop="port" />
<el-table-column :label="$t('commons.table.status')" :min-width="120">
<template #default="{ row }">
<div v-if="row.port.indexOf('-') !== -1 && row.usedStatus">
<el-tag type="info" class="mt-1">
{{ $t('firewall.used') + ' * ' + row.usedPorts.length }}
<div v-if="isSinglePort(row.port)">
<el-tag type="success" v-if="row.usedStatus">
{{ $t('firewall.used') + ' (' + row.usedStatus + ')' }}
<el-icon
v-if="row.processInfo"
@click="showProcessDetail(row.processInfo.PID)"
style="margin-left: 4px; cursor: pointer; vertical-align: middle"
>
<Expand />
</el-icon>
</el-tag>
<el-popover placement="right" popper-class="limit-height-popover" :width="250">
<template #default>
<ul v-for="(item, index) in row.usedPorts" :key="index">
<li>{{ item }}</li>
</ul>
</template>
<template #reference>
<svg-icon iconName="p-xiangqing" class="svg-icon"></svg-icon>
</template>
</el-popover>
</div>
<div v-else>
<el-tag type="info" v-if="row.usedStatus">
<span v-if="row.usedStatus === 'inUsed'">{{ $t('firewall.used') }}</span>
<span v-else>{{ $t('firewall.used') + ' ' + row.usedStatus }}</span>
</el-tag>
<el-tag type="success" v-else>{{ $t('firewall.unUsed') }}</el-tag>
<el-tag type="info" v-else>{{ $t('firewall.unUsed') }}</el-tag>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :min-width="80" :label="$t('firewall.strategy')" prop="strategy">
Expand Down Expand Up @@ -163,6 +155,7 @@
<OpDialog ref="opRef" @search="search" />
<OperateDialog @search="search" ref="dialogRef" />
<ImportDialog @search="search" ref="dialogImportRef" />
<ProcessDetail ref="processDetailRef" />
</div>
</template>

Expand All @@ -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';

Expand All @@ -195,6 +192,9 @@ const fireStatusRef = ref();

const opRef = ref();
const dialogImportRef = ref();
const processDetailRef = ref();

const listeningProcesses = ref<Process.ListeningProcess[]>([]);

const data = ref();
const paginationConfig = reactive({
Expand All @@ -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;
Expand All @@ -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(() => {
Expand Down Expand Up @@ -381,6 +421,10 @@ const onExport = () => {
});
};

const showProcessDetail = (pid: number) => {
processDetailRef.value?.acceptParams(pid);
};

const buttons = [
{
label: i18n.global.t('commons.button.edit'),
Expand Down
Loading