Files
devicemanager/ts/snmp/snmp.classes.snmpprotocol.ts
2026-01-09 07:14:39 +00:00

440 lines
12 KiB
TypeScript

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<ISnmpOptions> = {
community: 'public',
version: 2,
timeout: 5000,
retries: 1,
port: 161,
};
/**
* SNMP Protocol handler using net-snmp
*/
export class SnmpProtocol {
private session: ReturnType<typeof plugins.netSnmp.createSession> | null = null;
private address: string;
private options: Required<ISnmpOptions>;
constructor(address: string, options?: ISnmpOptions) {
this.address = address;
this.options = { ...DEFAULT_OPTIONS, ...options };
}
/**
* Create SNMP session
*/
private getSession(): ReturnType<typeof plugins.netSnmp.createSession> {
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<ISnmpVarbind> {
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<ISnmpVarbind[]> {
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<ISnmpVarbind> {
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<ISnmpVarbind[]> {
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<ISnmpVarbind[]> {
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<ISnmpVarbind> {
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<boolean> {
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<number, TSnmpValueType> = {
[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<TSnmpValueType, number> = {
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;
}
}