import * as plugins from '../plugins.js'; /** * Common SNMP OIDs (Object Identifiers) */ export const SNMP_OIDS = { // System MIB (RFC 1213) sysDescr: '1.3.6.1.2.1.1.1.0', sysObjectID: '1.3.6.1.2.1.1.2.0', sysUpTime: '1.3.6.1.2.1.1.3.0', sysContact: '1.3.6.1.2.1.1.4.0', sysName: '1.3.6.1.2.1.1.5.0', sysLocation: '1.3.6.1.2.1.1.6.0', sysServices: '1.3.6.1.2.1.1.7.0', // IF-MIB - Interfaces ifNumber: '1.3.6.1.2.1.2.1.0', ifTable: '1.3.6.1.2.1.2.2', // Host resources hrSystemUptime: '1.3.6.1.2.1.25.1.1.0', hrMemorySize: '1.3.6.1.2.1.25.2.2.0', // UPS-MIB (RFC 1628) upsIdentManufacturer: '1.3.6.1.2.1.33.1.1.1.0', upsIdentModel: '1.3.6.1.2.1.33.1.1.2.0', upsBatteryStatus: '1.3.6.1.2.1.33.1.2.1.0', upsSecondsOnBattery: '1.3.6.1.2.1.33.1.2.2.0', upsEstimatedMinutesRemaining: '1.3.6.1.2.1.33.1.2.3.0', upsEstimatedChargeRemaining: '1.3.6.1.2.1.33.1.2.4.0', upsBatteryVoltage: '1.3.6.1.2.1.33.1.2.5.0', upsInputFrequency: '1.3.6.1.2.1.33.1.3.3.1.2', upsInputVoltage: '1.3.6.1.2.1.33.1.3.3.1.3', upsOutputSource: '1.3.6.1.2.1.33.1.4.1.0', upsOutputFrequency: '1.3.6.1.2.1.33.1.4.2.0', upsOutputVoltage: '1.3.6.1.2.1.33.1.4.4.1.2', upsOutputCurrent: '1.3.6.1.2.1.33.1.4.4.1.3', upsOutputPower: '1.3.6.1.2.1.33.1.4.4.1.4', upsOutputPercentLoad: '1.3.6.1.2.1.33.1.4.4.1.5', // Printer MIB prtGeneralPrinterName: '1.3.6.1.2.1.43.5.1.1.16.1', prtMarkerSuppliesLevel: '1.3.6.1.2.1.43.11.1.1.9', prtMarkerSuppliesMaxCapacity: '1.3.6.1.2.1.43.11.1.1.8', }; /** * SNMP value types */ export type TSnmpValueType = | 'OctetString' | 'Integer' | 'Counter' | 'Counter32' | 'Counter64' | 'Gauge' | 'Gauge32' | 'TimeTicks' | 'IpAddress' | 'ObjectIdentifier' | 'Null' | 'Opaque'; /** * SNMP varbind (variable binding) */ export interface ISnmpVarbind { oid: string; type: TSnmpValueType; value: unknown; } /** * SNMP session options */ export interface ISnmpOptions { /** Community string (v1/v2c) or username (v3) */ community?: string; /** SNMP version: 1, 2 (v2c), or 3 */ version?: 1 | 2 | 3; /** Request timeout in milliseconds */ timeout?: number; /** Number of retries */ retries?: number; /** Port (default: 161) */ port?: number; } const DEFAULT_OPTIONS: Required = { community: 'public', version: 2, timeout: 5000, retries: 1, port: 161, }; /** * SNMP Protocol handler using net-snmp */ export class SnmpProtocol { private session: ReturnType | null = null; private address: string; private options: Required; constructor(address: string, options?: ISnmpOptions) { this.address = address; this.options = { ...DEFAULT_OPTIONS, ...options }; } /** * Create SNMP session */ private getSession(): ReturnType { if (!this.session) { const snmpVersion = this.options.version === 1 ? plugins.netSnmp.Version1 : plugins.netSnmp.Version2c; this.session = plugins.netSnmp.createSession(this.address, this.options.community, { port: this.options.port, retries: this.options.retries, timeout: this.options.timeout, version: snmpVersion, }); } return this.session; } /** * Close SNMP session */ public close(): void { if (this.session) { this.session.close(); this.session = null; } } /** * GET operation - retrieve a single OID value */ public async get(oid: string): Promise { return new Promise((resolve, reject) => { const session = this.getSession(); session.get([oid], (error: Error | null, varbinds: unknown[]) => { if (error) { reject(error); return; } if (varbinds.length === 0) { reject(new Error(`No response for OID ${oid}`)); return; } const vb = varbinds[0] as { oid: string; type: number; value: unknown }; if (plugins.netSnmp.isVarbindError(vb)) { reject(new Error(`SNMP error for ${oid}: ${plugins.netSnmp.varbindError(vb)}`)); return; } resolve(this.parseVarbind(vb)); }); }); } /** * GET operation - retrieve multiple OID values */ public async getMultiple(oids: string[]): Promise { return new Promise((resolve, reject) => { const session = this.getSession(); session.get(oids, (error: Error | null, varbinds: unknown[]) => { if (error) { reject(error); return; } const results: ISnmpVarbind[] = []; for (const vb of varbinds) { const varbind = vb as { oid: string; type: number; value: unknown }; if (!plugins.netSnmp.isVarbindError(varbind)) { results.push(this.parseVarbind(varbind)); } } resolve(results); }); }); } /** * GETNEXT operation - get the next OID in the MIB tree */ public async getNext(oid: string): Promise { return new Promise((resolve, reject) => { const session = this.getSession(); session.getNext([oid], (error: Error | null, varbinds: unknown[]) => { if (error) { reject(error); return; } if (varbinds.length === 0) { reject(new Error(`No response for GETNEXT ${oid}`)); return; } const vb = varbinds[0] as { oid: string; type: number; value: unknown }; if (plugins.netSnmp.isVarbindError(vb)) { reject(new Error(`SNMP error for ${oid}: ${plugins.netSnmp.varbindError(vb)}`)); return; } resolve(this.parseVarbind(vb)); }); }); } /** * GETBULK operation (v2c/v3 only) - efficient retrieval of table rows */ public async getBulk( oids: string[], nonRepeaters: number = 0, maxRepetitions: number = 20 ): Promise { if (this.options.version === 1) { throw new Error('GETBULK is not supported in SNMPv1'); } return new Promise((resolve, reject) => { const session = this.getSession(); session.getBulk(oids, nonRepeaters, maxRepetitions, (error: Error | null, varbinds: unknown[]) => { if (error) { reject(error); return; } const results: ISnmpVarbind[] = []; for (const vb of varbinds) { const varbind = vb as { oid: string; type: number; value: unknown }; if (!plugins.netSnmp.isVarbindError(varbind)) { results.push(this.parseVarbind(varbind)); } } resolve(results); }); }); } /** * Walk operation - retrieve all OIDs under a tree */ public async walk(baseOid: string): Promise { return new Promise((resolve, reject) => { const session = this.getSession(); const results: ISnmpVarbind[] = []; session.walk( baseOid, 20, // maxRepetitions (varbinds: unknown[]) => { for (const vb of varbinds) { const varbind = vb as { oid: string; type: number; value: unknown }; if (!plugins.netSnmp.isVarbindError(varbind)) { results.push(this.parseVarbind(varbind)); } } }, (error: Error | null) => { if (error) { reject(error); return; } resolve(results); } ); }); } /** * SET operation - set an OID value */ public async set(oid: string, type: TSnmpValueType, value: unknown): Promise { return new Promise((resolve, reject) => { const session = this.getSession(); const snmpType = this.getSnmpType(type); const varbind = { oid, type: snmpType, value, }; session.set([varbind], (error: Error | null, varbinds: unknown[]) => { if (error) { reject(error); return; } if (varbinds.length === 0) { reject(new Error(`No response for SET ${oid}`)); return; } const vb = varbinds[0] as { oid: string; type: number; value: unknown }; if (plugins.netSnmp.isVarbindError(vb)) { reject(new Error(`SNMP error for ${oid}: ${plugins.netSnmp.varbindError(vb)}`)); return; } resolve(this.parseVarbind(vb)); }); }); } /** * Get system information */ public async getSystemInfo(): Promise<{ sysDescr: string; sysObjectID: string; sysUpTime: number; sysContact: string; sysName: string; sysLocation: string; }> { const oids = [ SNMP_OIDS.sysDescr, SNMP_OIDS.sysObjectID, SNMP_OIDS.sysUpTime, SNMP_OIDS.sysContact, SNMP_OIDS.sysName, SNMP_OIDS.sysLocation, ]; const varbinds = await this.getMultiple(oids); const getValue = (oid: string): unknown => { const vb = varbinds.find((v) => v.oid === oid); return vb?.value; }; return { sysDescr: String(getValue(SNMP_OIDS.sysDescr) || ''), sysObjectID: String(getValue(SNMP_OIDS.sysObjectID) || ''), sysUpTime: Number(getValue(SNMP_OIDS.sysUpTime) || 0), sysContact: String(getValue(SNMP_OIDS.sysContact) || ''), sysName: String(getValue(SNMP_OIDS.sysName) || ''), sysLocation: String(getValue(SNMP_OIDS.sysLocation) || ''), }; } /** * Check if device is reachable via SNMP */ public async isReachable(): Promise { try { await this.get(SNMP_OIDS.sysDescr); return true; } catch { return false; } } /** * Parse varbind to our format */ private parseVarbind(vb: { oid: string; type: number; value: unknown }): ISnmpVarbind { return { oid: vb.oid, type: this.getTypeName(vb.type), value: this.parseValue(vb.type, vb.value), }; } /** * Get type name from SNMP type number */ private getTypeName(type: number): TSnmpValueType { const typeMap: Record = { [plugins.netSnmp.ObjectType.OctetString]: 'OctetString', [plugins.netSnmp.ObjectType.Integer]: 'Integer', [plugins.netSnmp.ObjectType.Counter]: 'Counter', [plugins.netSnmp.ObjectType.Counter32]: 'Counter32', [plugins.netSnmp.ObjectType.Counter64]: 'Counter64', [plugins.netSnmp.ObjectType.Gauge]: 'Gauge', [plugins.netSnmp.ObjectType.Gauge32]: 'Gauge32', [plugins.netSnmp.ObjectType.TimeTicks]: 'TimeTicks', [plugins.netSnmp.ObjectType.IpAddress]: 'IpAddress', [plugins.netSnmp.ObjectType.OID]: 'ObjectIdentifier', [plugins.netSnmp.ObjectType.Null]: 'Null', [plugins.netSnmp.ObjectType.Opaque]: 'Opaque', }; return typeMap[type] || 'OctetString'; } /** * Get SNMP type number from type name */ private getSnmpType(type: TSnmpValueType): number { const typeMap: Record = { OctetString: plugins.netSnmp.ObjectType.OctetString, Integer: plugins.netSnmp.ObjectType.Integer, Counter: plugins.netSnmp.ObjectType.Counter, Counter32: plugins.netSnmp.ObjectType.Counter32, Counter64: plugins.netSnmp.ObjectType.Counter64, Gauge: plugins.netSnmp.ObjectType.Gauge, Gauge32: plugins.netSnmp.ObjectType.Gauge32, TimeTicks: plugins.netSnmp.ObjectType.TimeTicks, IpAddress: plugins.netSnmp.ObjectType.IpAddress, ObjectIdentifier: plugins.netSnmp.ObjectType.OID, Null: plugins.netSnmp.ObjectType.Null, Opaque: plugins.netSnmp.ObjectType.Opaque, }; return typeMap[type]; } /** * Parse value based on type */ private parseValue(type: number, value: unknown): unknown { // OctetString - convert Buffer to string if (type === plugins.netSnmp.ObjectType.OctetString && Buffer.isBuffer(value)) { return value.toString('utf8'); } return value; } }