# SNMPAgent.java

    /*
     *  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.synapse.commons.snmp;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.snmp4j.TransportMapping;
    import org.snmp4j.agent.BaseAgent;
    import org.snmp4j.agent.CommandProcessor;
    import org.snmp4j.agent.DuplicateRegistrationException;
    import org.snmp4j.agent.ManagedObject;
    import org.snmp4j.agent.io.ImportModes;
    import org.snmp4j.agent.mo.MOTableRow;
    import org.snmp4j.agent.mo.snmp.*;
    import org.snmp4j.agent.security.MutableVACM;
    import org.snmp4j.log.Log4jLogFactory;
    import org.snmp4j.mp.MPv3;
    import org.snmp4j.mp.SnmpConstants;
    import org.snmp4j.security.*;
    import org.snmp4j.smi.*;
    import org.snmp4j.transport.TransportMappings;

    import javax.management.*;

    import java.io.File;
    import java.io.IOException;
    import java.lang.management.ManagementFactory;
    import java.net.BindException;
    import java.util.*;

    /**
     * SNMP agent which is capable of listening for incoming SNMP GET/GETNEXT requests
     * and responding to them accordingly. This agent implementation exposed all the
     * standard Synapse MBeans over SNMP. The view exposed by the agent is read-only as of
     * now (this may be changed in a future version). The relevant OID mappings are
     * defined in the SynapseMIBUtils class. Each MBean attribute becomes a leaf in the MIB
     * exposed by the agent. For each MBean, attributes are arranged in the alphabetical
     * order for OID assignment. MBean APIs rarely change. Therefore this scheme will
     * guarantee a fairly consistent OID scheme.
     */
    class SNMPAgent extends BaseAgent {

        private static final Log log = LogFactory.getLog(SNMPAgent.class);

        private static final String FULL_READ_VIEW = "fullReadView";
        private static final String GROUP_NAME = "synapseSNMPGroup";
        private static final String COMMUNITY_RECORD = "public2public";

        private Properties properties;

        private Set<OID> registeredOIDs = new HashSet<OID>();
        private int snmpVersion;

        // initialize Log4J logging
        static {
            org.snmp4j.log.LogFactory.setLogFactory(new Log4jLogFactory());
        }

        public SNMPAgent(Properties properties) {
            super(new File(SNMPConstants.BC_FILE), new File(SNMPConstants.CONFIG_FILE),
                    new CommandProcessor(new OctetString(MPv3.createLocalEngineID())));
            this.properties = properties;

            String version = getProperty(SNMPConstants.SNMP_VERSION, SNMPConstants.SNMP_DEFAULT_VERSION);
            if (SNMPConstants.SNMP_VERSION_1.equals(version)) {
                this.snmpVersion = SnmpConstants.version1;
            } else if (SNMPConstants.SNMP_VERSION_2_C.equals(version)) {
                this.snmpVersion = SnmpConstants.version2c;
            } else {
                log.warn("Unsupported SNMP version: " + version + " - Using defaults");
                this.snmpVersion = SnmpConstants.version1;
            }

            // Override the default sysOID and set the WSO2 OID...
            this.sysOID = new OID(SNMPConstants.SYNAPSE_OID_BRANCH);

            setSysDescr(new OctetString(getProperty(SNMPConstants.SNMP_DESCRIPTION, SNMPConstants.SNMP_DEFAULT_DESCRIPTION)));
        }

        @Override
        protected void registerSnmpMIBs() {
            this.snmpv2MIB.setContact(new OctetString(getProperty(SNMPConstants.SNMP_CONTACT_NAME, SNMPConstants.SNMP_DEFAULT_CONTACT_NAME)));
            this.snmpv2MIB.setLocation(new OctetString(getProperty(SNMPConstants.SNMP_LOCATION, SNMPConstants.SNMP_DEFAULT_LOCATION)));
            this.snmpv2MIB.setName(new OctetString(getProperty(SNMPConstants.SNMP_HOST, SNMPConstants.SNMP_DEFAULT_HOST)));

            super.registerSnmpMIBs();
        }

        /**
         * Initialize and start this SNMP agent
         *
         * @throws IOException If an error occurs while initializing the agent
         */
        public void start() throws IOException {
            String context = getProperty(SNMPConstants.SNMP_CONTEXT_NAME,
                    SNMPConstants.SNMP_DEFAULT_CONTEXT_NAME);
            try{
                init();
            } catch (Exception e) {
                if (e.getCause() != null && e.getCause() instanceof BindException) {
                    log.info("SNMP agent is already running, not initializing again");
                } else {
                    log.error("Unable to initialize SNMP agent", e);
                }
            }
            loadConfig(ImportModes.REPLACE_CREATE);
            addShutdownHook();
            getServer().addContext(new OctetString(context));
            finishInit();
            run();
            sendColdStartNotification();
        }

        @Override
        protected void initTransportMappings() throws IOException {
            String host = getProperty(SNMPConstants.SNMP_HOST, SNMPConstants.SNMP_DEFAULT_HOST);
            int port = Integer.parseInt(getProperty(SNMPConstants.SNMP_PORT,
                    String.valueOf(SNMPConstants.SNMP_DEFAULT_PORT)));
            String address = host + "/" + port;
            Address adr = GenericAddress.parse(address);
            TransportMapping tm =
                    TransportMappings.getInstance().createTransportMapping(adr);
            transportMappings = new TransportMapping[] { tm };
            log.info("SNMP transport adapter initialized on udp:" + address);
        }

        @Override
        protected void registerManagedObjects() {
            log.info("Initializing Synapse SNMP MIB");
            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
            Set<ObjectInstance> instances = mbs.queryMBeans(null, null);

            try {
                for (ObjectInstance instance : instances) {
                    ObjectName objectName = instance.getObjectName();
                    if (objectName.getDomain().equals("org.apache.synapse")) {
                        String oidString = SynapseMIBUtils.getOID(objectName);
                        if (oidString == null) {
                            continue;
                        }

                        MBeanInfo info = mbs.getMBeanInfo(objectName);
                        MBeanAttributeInfo[] attributes = info.getAttributes();
                        List<String> attributeNames = new ArrayList<String>();
                        List<String> mapAttributes = new ArrayList<String>();
                        for (MBeanAttributeInfo attributeInfo : attributes) {
                            attributeNames.add(attributeInfo.getName());
                            if (Map.class.getName().equals(attributeInfo.getType())) {
                                mapAttributes.add(attributeInfo.getName());
                            }
                        }
                        Collections.sort(attributeNames);

                        doRegister(attributeNames, mapAttributes, oidString, objectName);
                    }
                }
            } catch (Exception e) {
                log.error("Error while initializing the SNMP MIB", e);
            }
        }

        private void doRegister(List<String> attributeNames, List<String> mapAttributes,
                                String oidString, ObjectName objectName) {

            for (int i = 0; i < attributeNames.size(); i++) {
                String attributeName = attributeNames.get(i);
                if (mapAttributes.contains(attributeName)) {
                    continue;
                }
                OID oid = new OID(oidString + "." + (i + 1) + ".0");
                if (log.isDebugEnabled()) {
                    log.debug("Registering " + objectName + "@" + attributeName +
                            " as OID: " + oid);
                }
                try {
                    server.register(new SynapseMOScalar(
                            oid, objectName, attributeName, snmpVersion), null);
                    registeredOIDs.add(oid);
                } catch (DuplicateRegistrationException e) {
                    log.error("Error while registering the OID: " + oid + " for object: " +
                            objectName + " and attribute: " + attributeName, e);
                }
            }
        }

        @Override
        protected void unregisterManagedObjects() {
            if (log.isDebugEnabled()) {
                log.debug("Cleaning up registered OIDs");
            }

            for (OID oid : registeredOIDs) {
                ManagedObject mo = server.getManagedObject(oid, null);
                if (mo != null) {
                    server.unregister(mo, null);
                }
            }
            registeredOIDs.clear();
        }

        @Override
        protected void addUsmUser(USM usm) {

        }

        @Override
        protected void addNotificationTargets(SnmpTargetMIB snmpTargetMIB,
                                              SnmpNotificationMIB snmpNotificationMIB) {

        }

        @Override
        protected void addViews(VacmMIB vacm) {
            String communityString = getProperty(SNMPConstants.SNMP_COMMUNITY_NAME,
                    SNMPConstants.SNMP_DEFAULT_COMMUNITY_NAME);
            String securityName = getProperty(SNMPConstants.SNMP_SECURITY_NAME,
                    SNMPConstants.SNMP_DEFAULT_SECURITY_NAME);

            int securityModel = SecurityModel.SECURITY_MODEL_SNMPv1;
            if (snmpVersion == SnmpConstants.version2c) {
                securityModel = SecurityModel.SECURITY_MODEL_SNMPv2c;
            }

            vacm.addGroup(securityModel,
                    new OctetString(securityName),
                    new OctetString(GROUP_NAME),
                    StorageType.nonVolatile);

            vacm.addAccess(new OctetString(GROUP_NAME), new OctetString(communityString),
                    securityModel,
                    SecurityLevel.NOAUTH_NOPRIV,
                    MutableVACM.VACM_MATCH_EXACT,
                    new OctetString(FULL_READ_VIEW), // read permission granted
                    new OctetString(),               // no write permissions
                    new OctetString(),               // no notify permissions
                    StorageType.nonVolatile);

            vacm.addViewTreeFamily(new OctetString(FULL_READ_VIEW),
                    new OID(SNMPConstants.SYNAPSE_OID_BRANCH),
                    new OctetString(),
                    VacmMIB.vacmViewIncluded,
                    StorageType.nonVolatile);

            vacm.addViewTreeFamily(new OctetString(FULL_READ_VIEW), new OID("1.3.6"),
                                   new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile);

            vacm.addViewTreeFamily(new OctetString(FULL_READ_VIEW), new OID("1.3.6.1.2.1.1"),
                                   new OctetString(), VacmMIB.vacmViewIncluded, StorageType.nonVolatile);
        }

        @Override
        protected void addCommunities(SnmpCommunityMIB communityMIB) {
            String community = getProperty(SNMPConstants.SNMP_COMMUNITY_NAME,
                    SNMPConstants.SNMP_DEFAULT_COMMUNITY_NAME);
            String securityName = getProperty(SNMPConstants.SNMP_SECURITY_NAME,
                    SNMPConstants.SNMP_DEFAULT_SECURITY_NAME);
            String context = getProperty(SNMPConstants.SNMP_CONTEXT_NAME,
                    SNMPConstants.SNMP_DEFAULT_CONTEXT_NAME);

            if (log.isDebugEnabled()) {
                log.debug("Registering SNMP community string: " + community + " under the " +
                        "context: " + context);
            }

            Variable[] com2sec = new Variable[] {
                    new OctetString(community),              // community name
                    new OctetString(securityName),           // security name
                    getAgent().getContextEngineID(),         // local engine ID
                    new OctetString(context),                // default context name
                    new OctetString(),                       // transport tag
                    new Integer32(StorageType.nonVolatile),  // storage type
                    new Integer32(RowStatus.active)          // row status
            };
            MOTableRow row =
                    communityMIB.getSnmpCommunityEntry().createRow(
                            new OctetString(COMMUNITY_RECORD).toSubIndex(true), com2sec);
            communityMIB.getSnmpCommunityEntry().addRow(row);
        }

        private String getProperty(String name, String def) {
            String value = properties.getProperty(name);
            if (value == null) {
                value = def;
            }
            return value;
        }
    }