72 lines
1.9 KiB
TypeScript
72 lines
1.9 KiB
TypeScript
|
|
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: [] }
|
||
|
|
}
|
||
|
|
}
|