dev_arc_aws/backend/src/ssh/metrics/ports.ts

72 lines
1.9 KiB
TypeScript
Raw Normal View History

import type { Client } from 'ssh2'
import { execCommand } from './common.js'
export interface ListeningPort {
protocol: 'tcp' | 'udp'
localAddress: string
localPort: number
state?: string
pid?: number
process?: string
}
export interface PortsMetrics {
source: 'ss' | 'netstat' | 'none'
ports: ListeningPort[]
}
function parseSsOutput(output: string): ListeningPort[] {
const ports: ListeningPort[] = []
const lines = output.split('\n').slice(1)
for (const line of lines) {
const parts = line.trim().split(/\s+/)
if (parts.length < 5) continue
const protocol = parts[0]?.toLowerCase()
if (protocol !== 'tcp' && protocol !== 'udp') continue
const state = parts[1]
const localAddr = parts[4]
if (!localAddr) continue
const lastColon = localAddr.lastIndexOf(':')
if (lastColon === -1) continue
const address = localAddr.substring(0, lastColon).replace(/^\[|\]$/g, '')
const port = parseInt(localAddr.substring(lastColon + 1), 10)
if (isNaN(port)) continue
const entry: ListeningPort = {
protocol,
localAddress: address,
localPort: port,
state: protocol === 'tcp' ? state : undefined,
}
const processInfo = parts[6]
if (processInfo?.startsWith('users:')) {
const pidMatch = processInfo.match(/pid=(\d+)/)
const nameMatch = processInfo.match(/\("([^"]+)"/)
if (pidMatch) entry.pid = parseInt(pidMatch[1], 10)
if (nameMatch) entry.process = nameMatch[1]
}
ports.push(entry)
}
return ports
}
export async function collectPortsMetrics(client: Client): Promise<PortsMetrics> {
try {
const ssResult = await execCommand(client, 'ss -tulnp 2>/dev/null')
if (ssResult.stdout.includes('Local')) {
return { source: 'ss', ports: parseSsOutput(ssResult.stdout).sort((a, b) => a.localPort - b.localPort) }
}
return { source: 'none', ports: [] }
} catch {
return { source: 'none', ports: [] }
}
}