/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.nifi.snmp.processors; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.nifi.annotation.lifecycle.OnStopped; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationResult; import org.apache.nifi.processor.AbstractProcessor; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSession; import org.apache.nifi.processor.Processor; import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.util.StandardValidators; import org.snmp4j.AbstractTarget; import org.snmp4j.CommunityTarget; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; import org.snmp4j.UserTarget; import org.snmp4j.mp.MPv3; import org.snmp4j.mp.SnmpConstants; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; import org.snmp4j.security.UsmUser; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.UdpAddress; import org.snmp4j.transport.DefaultUdpTransportMapping; /** * Base processor that uses SNMP4J client API * (http://www.snmp4j.org/) * * @param <T> the type of {@link SNMPWorker}. Please see {@link SNMPSetter} * and {@link SNMPGetter} */ abstract class AbstractSNMPProcessor<T extends SNMPWorker> extends AbstractProcessor { /** property to define host of the SNMP agent */ public static final PropertyDescriptor HOST = new PropertyDescriptor.Builder() .name("snmp-hostname") .displayName("Host Name") .description("Network address of SNMP Agent (e.g., localhost)") .required(true) .defaultValue("localhost") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); /** property to define port of the SNMP agent */ public static final PropertyDescriptor PORT = new PropertyDescriptor.Builder() .name("snmp-port") .displayName("Port") .description("Numeric value identifying Port of SNMP Agent (e.g., 161)") .required(true) .defaultValue("161") .addValidator(StandardValidators.PORT_VALIDATOR) .build(); /** property to define SNMP version to use */ public static final PropertyDescriptor SNMP_VERSION = new PropertyDescriptor.Builder() .name("snmp-version") .displayName("SNMP Version") .description("SNMP Version to use") .required(true) .allowableValues("SNMPv1", "SNMPv2c", "SNMPv3") .defaultValue("SNMPv1") .build(); /** property to define SNMP community to use */ public static final PropertyDescriptor SNMP_COMMUNITY = new PropertyDescriptor.Builder() .name("snmp-community") .displayName("SNMP Community (v1 & v2c)") .description("SNMP Community to use (e.g., public)") .required(false) .defaultValue("public") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); /** property to define SNMP security level to use */ public static final PropertyDescriptor SNMP_SECURITY_LEVEL = new PropertyDescriptor.Builder() .name("snmp-security-level") .displayName("SNMP Security Level") .description("SNMP Security Level to use") .required(true) .allowableValues("noAuthNoPriv", "authNoPriv", "authPriv") .defaultValue("authPriv") .build(); /** property to define SNMP security name to use */ public static final PropertyDescriptor SNMP_SECURITY_NAME = new PropertyDescriptor.Builder() .name("snmp-security-name") .displayName("SNMP Security name / user name") .description("Security name used for SNMP exchanges") .required(false) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .build(); /** property to define SNMP authentication protocol to use */ public static final PropertyDescriptor SNMP_AUTH_PROTOCOL = new PropertyDescriptor.Builder() .name("snmp-authentication-protocol") .displayName("SNMP Authentication Protocol") .description("SNMP Authentication Protocol to use") .required(true) .allowableValues("MD5", "SHA", "") .defaultValue("") .build(); /** property to define SNMP authentication password to use */ public static final PropertyDescriptor SNMP_AUTH_PASSWORD = new PropertyDescriptor.Builder() .name("snmp-authentication-passphrase") .displayName("SNMP Authentication pass phrase") .description("Pass phrase used for SNMP authentication protocol") .required(false) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(true) .build(); /** property to define SNMP private protocol to use */ public static final PropertyDescriptor SNMP_PRIV_PROTOCOL = new PropertyDescriptor.Builder() .name("snmp-private-protocol") .displayName("SNMP Private Protocol") .description("SNMP Private Protocol to use") .required(true) .allowableValues("DES", "3DES", "AES128", "AES192", "AES256", "") .defaultValue("") .build(); /** property to define SNMP private password to use */ public static final PropertyDescriptor SNMP_PRIV_PASSWORD = new PropertyDescriptor.Builder() .name("snmp-private-protocol-passphrase") .displayName("SNMP Private protocol pass phrase") .description("Pass phrase used for SNMP private protocol") .required(false) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(true) .build(); /** property to define the number of SNMP retries when requesting the SNMP Agent */ public static final PropertyDescriptor SNMP_RETRIES = new PropertyDescriptor.Builder() .name("snmp-retries") .displayName("Number of retries") .description("Set the number of retries when requesting the SNMP Agent") .required(true) .defaultValue("0") .addValidator(StandardValidators.INTEGER_VALIDATOR) .build(); /** property to define the timeout when requesting the SNMP Agent */ public static final PropertyDescriptor SNMP_TIMEOUT = new PropertyDescriptor.Builder() .name("snmp-timeout") .displayName("Timeout (ms)") .description("Set the timeout (in milliseconds) when requesting the SNMP Agent") .required(true) .defaultValue("5000") .addValidator(StandardValidators.INTEGER_VALIDATOR) .build(); /** list of property descriptors */ static List<PropertyDescriptor> descriptors = new ArrayList<>(); /* * Will ensure that list of PropertyDescriptors is build only once, since * all other life cycle methods are invoked multiple times. */ static { descriptors.add(HOST); descriptors.add(PORT); descriptors.add(SNMP_VERSION); descriptors.add(SNMP_COMMUNITY); descriptors.add(SNMP_SECURITY_LEVEL); descriptors.add(SNMP_SECURITY_NAME); descriptors.add(SNMP_AUTH_PROTOCOL); descriptors.add(SNMP_AUTH_PASSWORD); descriptors.add(SNMP_PRIV_PROTOCOL); descriptors.add(SNMP_PRIV_PASSWORD); descriptors.add(SNMP_RETRIES); descriptors.add(SNMP_TIMEOUT); } /** SNMP target */ protected volatile AbstractTarget snmpTarget; /** transport mapping */ protected volatile TransportMapping transportMapping; /** SNMP */ protected volatile Snmp snmp; /** target resource */ protected volatile T targetResource; /** * Will builds target resource upon first invocation and will delegate to the * implementation of {@link #onTriggerSnmp(ProcessContext, ProcessSession)} method for * further processing. */ @Override public void onTrigger(ProcessContext context, ProcessSession session) throws ProcessException { synchronized (this) { this.buildTargetResource(context); } this.onTriggerSnmp(context, session); } /** * Will close current SNMP mapping. */ @OnStopped public void close() { try { if (this.targetResource != null) { this.targetResource.close(); } } catch (Exception e) { this.getLogger().warn("Failure while closing target resource " + this.targetResource, e); } this.targetResource = null; try { if (this.transportMapping != null) { this.transportMapping.close(); } } catch (IOException e) { this.getLogger().warn("Failure while closing UDP transport mapping", e); } this.transportMapping = null; try { if (this.snmp != null) { this.snmp.close(); } } catch (IOException e) { this.getLogger().warn("Failure while closing UDP transport mapping", e); } this.snmp = null; } /** * @see org.apache.nifi.components.AbstractConfigurableComponent#customValidate(org.apache.nifi.components.ValidationContext) */ @Override protected Collection<ValidationResult> customValidate(ValidationContext validationContext) { final List<ValidationResult> problems = new ArrayList<>(super.customValidate(validationContext)); final boolean isVersion3 = "SNMPv3".equals(validationContext.getProperty(SNMP_VERSION).getValue()); if(isVersion3) { final boolean isSecurityNameSet = validationContext.getProperty(SNMP_SECURITY_NAME).isSet(); if(!isSecurityNameSet) { problems.add(new ValidationResult.Builder() .input("SNMP Security Name") .valid(false) .explanation("SNMP Security Name must be set with SNMPv3.") .build()); } final boolean isAuthProtOK = !"".equals(validationContext.getProperty(SNMP_AUTH_PROTOCOL).getValue()); final boolean isAuthPwdSet = validationContext.getProperty(SNMP_AUTH_PASSWORD).isSet(); final boolean isPrivProtOK = !"".equals(validationContext.getProperty(SNMP_PRIV_PROTOCOL).getValue()); final boolean isPrivPwdSet = validationContext.getProperty(SNMP_PRIV_PASSWORD).isSet(); switch(validationContext.getProperty(SNMP_SECURITY_LEVEL).getValue()) { case "authNoPriv": if(!isAuthProtOK || !isAuthPwdSet) { problems.add(new ValidationResult.Builder() .input("SNMP Security Level") .valid(false) .explanation("Authentication protocol and password must be set when using authNoPriv security level.") .build()); } break; case "authPriv": if(!isAuthProtOK || !isAuthPwdSet || !isPrivProtOK || !isPrivPwdSet) { problems.add(new ValidationResult.Builder() .input("SNMP Security Level") .valid(false) .explanation("All protocols and passwords must be set when using authPriv security level.") .build()); } break; case "noAuthNoPriv": default: break; } } else { final boolean isCommunitySet = validationContext.getProperty(SNMP_COMMUNITY).isSet(); if(!isCommunitySet) { problems.add(new ValidationResult.Builder() .input("SNMP Community") .valid(false) .explanation("SNMP Community must be set with SNMPv1 and SNMPv2c.") .build()); } } return problems; } /** * Delegate method to supplement * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by * sub-classes to perform {@link Processor} specific functionality. * * @param context * instance of {@link ProcessContext} * @param session * instance of {@link ProcessSession} * @throws ProcessException Process exception */ protected abstract void onTriggerSnmp(ProcessContext context, ProcessSession session) throws ProcessException; /** * Delegate method to supplement building of target {@link SNMPWorker} (see * {@link SNMPSetter} or {@link SNMPGetter}) and is implemented by * sub-classes. * * @param context * instance of {@link ProcessContext} * @return new instance of {@link SNMPWorker} */ protected abstract T finishBuildingTargetResource(ProcessContext context); /** * Builds target resource. * @param context Process context */ private void buildTargetResource(ProcessContext context) { if((this.transportMapping == null) || !this.transportMapping.isListening() || (this.snmp == null)) { try { this.transportMapping = new DefaultUdpTransportMapping(); this.snmp = new Snmp(this.transportMapping); if("SNMPv3".equals(context.getProperty(SNMP_VERSION).getValue())) { USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); SecurityModels.getInstance().addSecurityModel(usm); } this.transportMapping.listen(); } catch (Exception e) { throw new IllegalStateException("Failed to initialize UDP transport mapping", e); } } if (this.snmpTarget == null) { this.snmpTarget = this.createSnmpTarget(context); } if (this.targetResource == null) { this.targetResource = this.finishBuildingTargetResource(context); } } /** * Creates {@link AbstractTarget} to request SNMP agent. * @param context Process context * @return the SNMP target */ private AbstractTarget createSnmpTarget(ProcessContext context) { AbstractTarget result = null; String snmpVersion = context.getProperty(SNMP_VERSION).getValue(); int version = 0; switch (snmpVersion) { case "SNMPv2c": version = SnmpConstants.version2c; break; case "SNMPv3": version = SnmpConstants.version3; break; case "SNMPv1": default: version = SnmpConstants.version1; break; } if(version == SnmpConstants.version3) { final String username = context.getProperty(SNMP_SECURITY_NAME).getValue(); final String authPassword = context.getProperty(SNMP_AUTH_PASSWORD).getValue(); final String privPassword = context.getProperty(SNMP_PRIV_PASSWORD).getValue(); final String authProtocol = context.getProperty(SNMP_AUTH_PROTOCOL).getValue(); final String privProtocol = context.getProperty(SNMP_PRIV_PROTOCOL).getValue(); OctetString aPwd = authPassword != null ? new OctetString(authPassword) : null; OctetString pPwd = privPassword != null ? new OctetString(privPassword) : null; // add user information this.snmp.getUSM().addUser(new OctetString(username), new UsmUser(new OctetString(username), SNMPUtils.getAuth(authProtocol), aPwd, SNMPUtils.getPriv(privProtocol), pPwd)); result = new UserTarget(); ((UserTarget) result).setSecurityLevel(SNMPUtils.getSecLevel(context.getProperty(SNMP_SECURITY_LEVEL).getValue())); final String securityName = context.getProperty(SNMP_SECURITY_NAME).getValue(); if(securityName != null) { ((UserTarget) result).setSecurityName(new OctetString(securityName)); } } else { result = new CommunityTarget(); String community = context.getProperty(SNMP_COMMUNITY).getValue(); if(community != null) { ((CommunityTarget) result).setCommunity(new OctetString(community)); } } result.setVersion(version); result.setAddress(new UdpAddress(context.getProperty(HOST).getValue() + "/" + context.getProperty(PORT).getValue())); result.setRetries(context.getProperty(SNMP_RETRIES).asInteger()); result.setTimeout(context.getProperty(SNMP_TIMEOUT).asInteger()); return result; } }