All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sentrysoftware.snmp.client.SnmpClient Maven / Gradle / Ivy

The newest version!
/*-
 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
 * SNMP Java Client
 * ჻჻჻჻჻჻
 * Copyright 2023 Sentry Software
 * ჻჻჻჻჻჻
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
 */

package org.sentrysoftware.snmp.client;

import uk.co.westhawk.snmp.pdu.BlockPdu;
import uk.co.westhawk.snmp.stack.AsnObject;
import uk.co.westhawk.snmp.stack.AsnObjectId;
import uk.co.westhawk.snmp.stack.AsnOctets;
import uk.co.westhawk.snmp.stack.PduException;
import uk.co.westhawk.snmp.stack.SnmpConstants;
import uk.co.westhawk.snmp.stack.SnmpContext;
import uk.co.westhawk.snmp.stack.SnmpContextv2c;
import uk.co.westhawk.snmp.stack.SnmpContextv3;
import uk.co.westhawk.snmp.stack.SnmpContextv3Face;
import uk.co.westhawk.snmp.stack.varbind;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class SnmpClient {

	// Default SNMP port number
	public static final int SNMP_PORT = 161;

	// SNMP v1, v2c, v3
	public static final int SNMP_V1 = 1;
	public static final int SNMP_V2C = 2;
	public static final int SNMP_V3 = 3;
	public static final String SNMP_AUTH_MD5 = "MD5";
	public static final String SNMP_AUTH_SHA = "SHA";
	public static final String SNMP_PRIVACY_DES = "DES";
	public static final String SNMP_PRIVACY_AES = "AES";
	public static final String SNMP_NONE = "None";

	private SnmpContext contextv1 = null;
	private SnmpContextv2c contextv2c = null;
	private SnmpContextv3 contextv3 = null;
	private BlockPdu pdu;
	private String host;
	private int port;
	private String community;
	private int snmpVersion;
	private String authUsername;
	private String authType;
	private String authPassword;
	private String privacyType;
	private String privacyPassword;
	private int[] retryIntervals;
	private String contextName;
	private byte[] contextEngineID;
	public static final String SOCKET_TYPE = "Standard";

	/**
	 * Creates an SNMPClient instance, which connects to the specified SNMP agent with the specified credentials
	 * (depending on the version of SNMP)
	 * @param host				The hostname/IP address of the SNMP agent we're querying
	 * @param port				The port of the SNMP agent (should be 161)
	 * @param version			The version of SNMP to use (1, 2 or 3)
	 * @param retryIntervals	Timeout in milliseconds after which the elementary operations will be retried
	 * @param community			(SNMP v1 and v2 only) The SNMP community
	 * @param authType			(SNMP v3 only) The authentication method: "MD5", "SHA" or ""
	 * @param authUsername		(SNMP v3 only) The username
	 * @param authPassword		(SNMP v3 only) The password (in clear)
	 * @param privacyType		(SNMP v3 only) The encryption type: "DES", "AES" or ""
	 * @param privacyPassword	(SNMP v3 only) The encryption password
	 * @param contextName		(SNMP v3 only) The context name
	 * @param contextID			(SNMP v3 only) The context ID (??)
	 * @throws IllegalArgumentException when specified authType, privType are invalid
	 * @throws IllegalStateException when the specified properties lead to something that cannot work (i.e. privacy without authentication)
	 * @throws IOException when cannot initialize the SNMP context
	 */
	public SnmpClient(String host, int port, int version, int[] retryIntervals,
					  String community,
					  String authType, String authUsername, String authPassword,
					  String privacyType, String privacyPassword,
					  String contextName, byte[] contextID) throws IOException {
		// First, validate the inputs
		validate(version, authType, privacyType);

		// Sets the attributes of the class instance
		this.host = host;
		this.port = port;
		this.snmpVersion = version;
		this.retryIntervals = retryIntervals;
		this.community = community;
		this.authType = authType;
		this.authUsername = authUsername;
		this.authPassword = authPassword;
		this.privacyType = privacyType;
		this.privacyPassword = privacyPassword;
		this.contextName = contextName;
		this.contextEngineID = contextID;

		// Properly create the SNMP context, based on these properties
		initialize();
	}


	/**
	 * Validate the specified inputs. Throws an IllegalArgumentException if needed.
	 * @param version Specified version of the SNMP protocol
	 * @param authType Specified authType
	 * @param privacyType Specified privacyType
	 * @throws IllegalArgumentException when invalid inputs are specified
	 */
	private void validate(int version, String authType, String privacyType) {

		// In case of SNMP v3, check the authType and privType (if not empty)
		if (version == SNMP_V3) {
			if (authType != null) {
				if (!authType.isEmpty()) {
					if (!authType.equals(SNMP_AUTH_MD5) && !authType.equals(SNMP_AUTH_SHA)) {
						throw new IllegalArgumentException("Invalid authentication method '" + authType + "' (must be either '" + SNMP_AUTH_MD5 + "' or '" + SNMP_AUTH_SHA + "' or empty)");
					}
				}
			}

			if (privacyType != null) {
				if (!privacyType.isEmpty()) {
					if (!privacyType.equals(SNMP_PRIVACY_DES) && !privacyType.equals(SNMP_PRIVACY_AES)) {
						throw new IllegalArgumentException("Invalid privacy method '" + privacyType +"' (must be either '" + SNMP_PRIVACY_DES + "' or '" + SNMP_PRIVACY_AES + "' or empty)");
					}
				}
			}
		}
	}

	/**
	 * Initialize the SNMPClient
	 * 

* Creates the context to connect to the SNMP agent. Required before any actual operation is performed. * @throws IOException when cannot create the SNMP context * @throws IllegalStateException when there is an inconsistency in the properties that prevent us from moving forward */ private void initialize() throws IOException { // SNMP v2c if (snmpVersion == SNMP_V2C) { contextv2c = new SnmpContextv2c(host, port, null, SOCKET_TYPE); contextv2c.setCommunity(community); } // SNMP v3 else if (snmpVersion == SNMP_V3) { int authProtocolCode = 0; int privacyProtocolCode = 0; boolean authenticate = false; boolean privacy = false; // Some sanity check with the "context" if (contextEngineID == null) { contextEngineID = new byte[0]; } if (contextName == null) { contextName = ""; } // Verify the username if (authUsername == null) { authUsername = ""; } // Verify and translate the authentication type if (authType == null || authUsername == null || authPassword == null) { authenticate = false; authProtocolCode = 4; authPassword = ""; } else if (authType.isEmpty() || authUsername.isEmpty() || authPassword.isEmpty()) { authenticate = false; authProtocolCode = 4; authPassword = ""; } else if (authType.equals(SNMP_AUTH_MD5)) { authenticate = true; authProtocolCode = SnmpContextv3Face.MD5_PROTOCOL; } else if (authType.equals(SNMP_AUTH_SHA)) { authenticate = true; authProtocolCode = SnmpContextv3Face.SHA1_PROTOCOL; } // Verify the privacy thing if (privacyType == null || privacyPassword == null) { privacy = false; } else if (privacyType.isEmpty() || privacyPassword.isEmpty()) { privacy = false; } else if (privacyType.equals(SNMP_PRIVACY_DES)) { privacy = true; privacyProtocolCode = SnmpContextv3Face.DES_ENCRYPT; } else if (privacyType.equals(SNMP_PRIVACY_AES)) { privacy = true; privacyProtocolCode = SnmpContextv3Face.AES_ENCRYPT; } // Privacy with no authentication is impossible if (privacy && !authenticate) { throw new IllegalStateException("Authentication is required for privacy to be enforced"); } // Create the context contextv3 = new SnmpContextv3(host, port, SOCKET_TYPE); contextv3.setContextEngineId(contextEngineID); contextv3.setContextName(contextName); contextv3.setUserName(authUsername); contextv3.setUseAuthentication(authenticate); if (authenticate) { contextv3.setUserAuthenticationPassword(authPassword); contextv3.setAuthenticationProtocol(authProtocolCode); contextv3.setUsePrivacy(privacy); if (privacy) { contextv3.setPrivacyProtocol(privacyProtocolCode); contextv3.setUserPrivacyPassword(privacyPassword); } } } // SNMP v1 (default) else { contextv1 = new SnmpContext(host, port, SOCKET_TYPE); contextv1.setCommunity(community); } // Small thing: set the prefix for hex values (default is "0x" but we're setting it to empty) AsnOctets.setHexPrefix(""); // AsnObject.setDebug(15); } /** * Releases the resources associated to this instance * (or so at least we believe...) */ public void freeResources() { if (contextv1 != null) { contextv1.destroy(); contextv1 = null;} if (contextv2c != null) { contextv2c.destroy(); contextv2c = null;} if (contextv3 != null) { contextv3.destroy(); contextv3 = null;} if (pdu != null) { pdu = null; } } /** * Create the PDU and sets the timeout *

Note: This method has been created just to avoid duplicate code in the get, getNext and walk functions */ private void createPdu() { // Create the PDU based on the proper context if (snmpVersion == SNMP_V2C) { pdu = new BlockPdu(contextv2c); } else if (snmpVersion == SNMP_V3) { pdu = new BlockPdu(contextv3); } else { pdu = new BlockPdu(contextv1); } // Set the timeout if (retryIntervals != null) { pdu.setRetryIntervals(retryIntervals); } } /** * Perform a GET operation on the specified OID * @param oid OID on which to perform a GET operation * @return Value of the specified OID * @throws Exception in case of any problem */ public String get(String oid) throws Exception { createPdu(); pdu.setPduType(BlockPdu.GET); pdu.addOid(oid); return sendRequest().value; } /** * Perform a GET operation on the specified OID and return the details of the result (including the type of the value) * @param oid OID on which to perform a GET operation * @return A string in the form of the OID, "string" and the value, separated by tabs (\t) * @throws Exception in case of any problem */ public String getWithDetails(String oid) throws Exception { createPdu(); pdu.setPduType(BlockPdu.GET); pdu.addOid(oid); SnmpResult result = sendRequest(); return result.oid + "\t" + result.type + "\t" + result.value; } /** * Perform a GETNEXT operation on the specified OID * @param oid OID on which to perform a GETNEXT operation * @return A string in the form of the OID, "string" and the value, separated by tabs (\t) * @throws Exception in case of any problem */ public String getNext(String oid) throws Exception { createPdu(); pdu.setPduType(BlockPdu.GETNEXT); pdu.addOid(oid); SnmpResult result = sendRequest(); return result.oid + "\t" + result.type + "\t" + result.value; } /** * Perform a WALK, i.e. a series of GETNEXT operations until we fall off the tree * @param oid Root OID of the tree * @return Result of the WALK operation, as a long String. Each pair of oid/value is separated with a linefeed (at least, for now!) * @throws Exception * @throws IllegalArgumentException for bad specified OIDs */ public String walk(String oid) throws Exception { StringBuilder walkResult = new StringBuilder(); String currentOID; SnmpResult getNextResult; // Sanity check? if (oid == null) { throw new IllegalArgumentException("Invalid SNMP Walk OID: null"); } if (oid.length() < 3) { throw new IllegalArgumentException("Invalid SNMP Walk OID: \"" + oid + "\""); } // Now, something special: // In the walk loop below, we will catch any exception and break out of the loop // if anything happens. At that point, we simply return what we have, i.e. just // as if everything was okay. Doing so, we fail to report authentication problems. // So, the code using this will think it's just okay, even though the QA team // intentionally put bad credentials to verify the error message... See MATSYA-464. // // So, we're going to first run a getNext() for nothing, just so that // this call will throw the proper exception in case of credentials problems. getNext(oid); currentOID = oid; do { createPdu(); pdu.setPduType(BlockPdu.GETNEXT); pdu.addOid(currentOID); try { getNextResult = sendRequest(); } catch (Exception e) { // Something wrong? Get out of the loop and return what we have break; } currentOID = getNextResult.oid; if (!currentOID.startsWith(oid)) { // We're off the tree, so get out of the loop break; } // Append the result walkResult.append(currentOID + "\t" + getNextResult.type + "\t" + getNextResult.value + "\n"); } while (walkResult.length() < 10 * 1048576); // 10 MB is the limit for the result of our WALK operation. Should be enough. // Remove the trailing \n (if any) int resultLength = walkResult.length(); if (resultLength > 0) { return walkResult.substring(0, resultLength - 1); } // If nothing, return an empty string return ""; } /** * Read the content of an SNMP table * @param rootOID Root OID of the SNMP table * @param selectColumnArray Array of numbers specifying the column numbers of the array to be read. Use "ID" for the row number. * @return A semicolon-separated list of values * @throws IllegalArgumentException when the specified arguments are wrong * @throws Exception when the underlying SNMP API throws one */ public List> table(String rootOID, String[] selectColumnArray) throws Exception { // Sanity check if (rootOID == null) { throw new IllegalArgumentException("Invalid SNMP Table OID: null"); } if (rootOID.length() < 3) { throw new IllegalArgumentException("Invalid SNMP Table OID: \"" + rootOID + "\""); } if (selectColumnArray == null) { throw new IllegalArgumentException("Invalid SNMP Table column numbers: null"); } if (selectColumnArray.length < 1) { throw new IllegalArgumentException("Invalid SNMP Table column numbers: none"); } // First of all, retrieve the list of IDs in the table // To do so, we need to see what is the first column number available (it may not be 1) createPdu(); pdu.setPduType(BlockPdu.GETNEXT); pdu.addOid(rootOID); String firstValueOid = sendRequest().oid; if (firstValueOid.isEmpty() || !firstValueOid.startsWith(rootOID)) { // Empty table return new ArrayList<>(); } int tempIndex = firstValueOid.indexOf(".", rootOID.length() + 2); if (tempIndex < 0) { // Weird case, there is no "." after the rootOID in the OID of the first value we successfully got in the table return new ArrayList<>(); } String firstColumnOid = firstValueOid.substring(0, tempIndex); int firstColumnOidLength = firstColumnOid.length(); // Now, find the list of row IDs in this column. We're going to do something like a walk, except we don't care about the values. Just the OIDs. ArrayList IDArray = new ArrayList(0); String currentOID = firstColumnOid; SnmpResult getNextResult; do { // Get next until we get out of the tree createPdu(); pdu.setPduType(BlockPdu.GETNEXT); pdu.addOid(currentOID); getNextResult = sendRequest(); currentOID = getNextResult.oid; // Outside? Exit! if (!currentOID.startsWith(firstColumnOid)) { break; } // Add the right part of the OID in the list of IDs (the part to the right of the column OID) IDArray.add(currentOID.substring(firstColumnOidLength + 1)); } while (IDArray.size() < 10000); // Not more than 10000 lines, please... // And finally, build the result table List> tableResult = new ArrayList<>(); for (String ID : IDArray) { // For each row... List row = new ArrayList<>(); for (String column : selectColumnArray) { // For each column... // If the column has to provide the ID of the row if (column.equals("ID")) { row.add(ID); } else { // Keep going, even in case of a failure try { row.add(get(rootOID + "." + column + "." + ID)); } catch (Exception e) { row.add(""); } } } tableResult.add(row); } // Return the result return tableResult; } /** * Sends the SNMP request and perform some minor interpretation of the result * @return Result of the query in the form of a couple {oid;value} (SnmpResult) * @throws PduException when an error happens at the SNMP layer * @throws IOException when an error occurs at the network layer * @throws Exception when we cannot get the value of the specified OID, because it does not exist *

*

  • In case of no such OID, an exception is thrown. *
  • In case of empty value, the result will have the oid and an empty value. */ private SnmpResult sendRequest() throws PduException, IOException, Exception { // Declarations SnmpResult result = new SnmpResult(); // Send the SNMP request varbind var = pdu.getResponseVariableBinding(); // Retrieve the OID and value of the response (a varbind) AsnObjectId oid = var.getOid(); AsnObject value = var.getValue(); // No such OID? Throw an exception (this needs to be caught gracefully by other functions) byte valueType = value.getRespType(); if (valueType == SnmpConstants.SNMP_VAR_NOSUCHOBJECT || valueType == SnmpConstants.SNMP_VAR_NOSUCHINSTANCE || valueType == SnmpConstants.SNMP_VAR_ENDOFMIBVIEW) { throw new Exception(value.getRespTypeString()); } // Empty? else if (valueType == SnmpConstants.ASN_NULL) { result.oid = oid.toString(); result.type = "null"; } // ASN_OCTET_STRING? (special case, because it may need to be displayed as an hexadecimal string) // Normally, the API should take care of that, but this is not the case for 0x00:00:00:00 values, which are displayed as empty values else if (valueType == SnmpConstants.ASN_OCTET_STR) { result.oid = oid.toString(); result.type = "ASN_OCTET_STR"; // Map the value to the specific AsnOctets sub-class AsnOctets octetStringValue = (AsnOctets)value; // First, convert the value as a string, using toString(). // AsnOctets.toString() will convert the value to a normal string (with ASCII chars) // or to the hexadecimal version of it, like 0x00:30:31:32 when the "normal" string is // not printable String octetStringValuetoString = octetStringValue.toString(); // Then forcibly convert the value to its hexadecimal representation, but replace ':' with blank spaces, to match with what PATROL does String octetStringValuetoHex = octetStringValue.toHex().replace(':', ' '); // So, if the toString() and the toHex() value have the same length, it means that toString() is actually returning // the hexadecimal representation (yeah, there is no other way to retrieve that, the SNMP API does not tell us // whether the value is printable or not) // If the SNMP API judges that the value is not printable, then we will use the toHex() value, with ':' replaced with blank spaces. // But there is another case: if the original value is just a series of 0x00 (nul) chars, the SNMP API converts that to an // empty string, while we need to actually display 00 00 00 00... // That's what we're doing in the code below // If octetStringValuetoString is empty while the original value's length was greater than 0, then we'll need to convert it to hexadecimal if (octetStringValuetoString.isEmpty() && octetStringValue.getBytes().length > 0) { result.value = octetStringValuetoHex; } else if (octetStringValuetoString.length() == octetStringValuetoHex.length()) { result.value = octetStringValuetoHex; } else { result.value = octetStringValuetoString; } } // Sets the result object else { result.oid = oid.toString(); result.type = value.getRespTypeString(); result.value = value.toString(); } return result; } // end of sendRequest }// end of class - SNMPClient




  • © 2015 - 2025 Weber Informatics LLC | Privacy Policy