/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2007-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) 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 Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.config; import static org.opennms.core.utils.InetAddressUtils.isInetAddressInRange; import static org.opennms.core.utils.InetAddressUtils.str; import static org.opennms.core.utils.InetAddressUtils.toIpAddrBytes; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringWriter; import java.net.InetAddress; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.exolab.castor.xml.MarshalException; import org.exolab.castor.xml.Marshaller; import org.exolab.castor.xml.ValidationException; import org.opennms.core.utils.ByteArrayComparator; import org.opennms.core.utils.InetAddressUtils; import org.opennms.core.utils.IpListFromUrl; import org.opennms.core.utils.LogUtils; import org.opennms.core.xml.CastorUtils; import org.opennms.netmgt.config.linkd.ExcludeRange; import org.opennms.netmgt.config.linkd.Filter; import org.opennms.netmgt.config.linkd.IncludeRange; import org.opennms.netmgt.config.linkd.Iproutes; import org.opennms.netmgt.config.linkd.LinkdConfiguration; import org.opennms.netmgt.config.linkd.Package; import org.opennms.netmgt.config.linkd.Vendor; import org.opennms.netmgt.config.linkd.Vlans; import org.opennms.netmgt.filter.FilterDaoFactory; import org.opennms.protocols.snmp.SnmpObjectId; /** * <p>Abstract LinkdConfigManager class.</p> * * @author <a href="mailto:antonio@opennms.it">Antonio Russo</a> * @version $Id: $ */ abstract public class LinkdConfigManager implements LinkdConfig { private final ReadWriteLock m_globalLock = new ReentrantReadWriteLock(); private final Lock m_readLock = m_globalLock.readLock(); private final Lock m_writeLock = m_globalLock.writeLock(); public static final String DEFAULT_IP_ROUTE_CLASS_NAME = "org.opennms.netmgt.linkd.snmp.IpRouteTable"; /** * Object containing all Linkd-configuration objects parsed from the XML * file */ protected static LinkdConfiguration m_config; /** * A mapping of the configured URLs to a list of the specific IPs configured * in each - so as to avoid file reads */ private static Map<String, List<String>> m_urlIPMap = new HashMap<String, List<String>>(); /** * A mapping of the configured package to a list of IPs selected via filter * rules, so as to avoid redundant database access. */ private static Map<org.opennms.netmgt.config.linkd.Package, List<InetAddress>> m_pkgIpMap = new HashMap<org.opennms.netmgt.config.linkd.Package, List<InetAddress>>(); /** * The HashMap that associates the OIDS masks to class name for Vlans */ private static Map<String,String> m_oidMask2VlanclassName = new HashMap<String,String>(); /** * The HashMap that associates the OIDS masks to class name for IpRoutes */ private static Map<String,String> m_oidMask2IpRouteclassName = new HashMap<String,String>(); /** * <p>Constructor for LinkdConfigManager.</p> * * @param stream a {@link java.io.InputStream} object. * @throws org.exolab.castor.xml.MarshalException if any. * @throws org.exolab.castor.xml.ValidationException if any. * @throws java.io.IOException if any. */ public LinkdConfigManager(final InputStream stream) throws MarshalException, ValidationException, IOException { reloadXML(stream); } public Lock getReadLock() { return m_readLock; } public Lock getWriteLock() { return m_writeLock; } /** * Whether autodiscovery is enabled in linkd-config (default: false) */ public boolean isAutoDiscoveryEnabled() { getReadLock().lock(); try { if (m_config.hasAutoDiscovery()) return m_config.getAutoDiscovery(); } finally { getReadLock().unlock(); } return false; } /** * Whether vlan discovery is enabled in linkd-config (default: true) */ public boolean isVlanDiscoveryEnabled() { getReadLock().lock(); try { if (m_config.hasEnableVlanDiscovery()) return m_config.getEnableVlanDiscovery(); } finally { getReadLock().unlock(); } return true; } /** * This method is used to determine if the named interface is included in * the passed package definition. If the interface belongs to the package * then a value of true is returned. If the interface does not belong to the * package a false value is returned. * * <strong>Note: </strong>Evaluation of the interface against a package * filter will only work if the IP is already in the database. * * @param iface * The interface to test against the package. * @param pkg * The package to check for the inclusion of the interface. * @return True if the interface is included in the package, false * otherwise. */ public boolean isInterfaceInPackage(final InetAddress iface, final org.opennms.netmgt.config.linkd.Package pkg) { boolean filterPassed = false; getReadLock().lock(); try { // get list of IPs in this package final List<InetAddress> ipList = m_pkgIpMap.get(pkg); if (ipList != null && ipList.size() > 0) { filterPassed = ipList.contains(iface); } LogUtils.debugf(this, "interfaceInPackage: Interface %s passed filter for package %s?: %s", str(iface), pkg.getName(), (filterPassed? "True":"False")); if (!filterPassed) return false; return isInterfaceInPackageRange(iface, pkg); } finally { getReadLock().unlock(); } } public boolean isInterfaceInPackageRange(final InetAddress iface, final org.opennms.netmgt.config.linkd.Package pkg) { if (pkg == null) return false; // // Ensure that the interface is in the specific list or // that it is in the include range and is not excluded // boolean has_specific = false; boolean has_range_include = false; boolean has_range_exclude = false; getReadLock().lock(); try { byte[] addr = iface.getAddress(); // if there are NO include ranges then treat act as if the user include // the range 0.0.0.0 - 255.255.255.255 has_range_include = pkg.getIncludeRangeCount() == 0 && pkg.getSpecificCount() == 0; // Specific wins; if we find one, return immediately. for (final String spec : pkg.getSpecificCollection()) { final byte[] speca = toIpAddrBytes(spec); if (new ByteArrayComparator().compare(addr, speca) == 0) { has_specific = true; break; } } if (has_specific) return true; for (final String url : pkg.getIncludeUrlCollection()) { has_specific = isInterfaceInUrl(iface, url); if (has_specific) break; } if (has_specific) return true; if (!has_range_include) { for (final IncludeRange rng : pkg.getIncludeRangeCollection()) { if (isInetAddressInRange(iface.getAddress(), rng.getBegin(), rng.getEnd())) { has_range_include = true; break; } } } for (final ExcludeRange rng : pkg.getExcludeRangeCollection()) { if (isInetAddressInRange(iface.getAddress(), rng.getBegin(), rng.getEnd())) { has_range_exclude = true; break; } } return has_range_include && !has_range_exclude; } finally { getReadLock().unlock(); } } /** * <p>enumeratePackage</p> * * @return a {@link java.util.Enumeration} object. */ public Enumeration<Package> enumeratePackage() { getReadLock().lock(); try { return getConfiguration().enumeratePackage(); } finally { getReadLock().unlock(); } } /** * Return the linkd configuration object. * * @return a {@link org.opennms.netmgt.config.linkd.LinkdConfiguration} object. */ public LinkdConfiguration getConfiguration() { getReadLock().lock(); try { return m_config; } finally { getReadLock().unlock(); } } /** {@inheritDoc} */ public org.opennms.netmgt.config.linkd.Package getPackage(final String name) { getReadLock().lock(); try { for (final org.opennms.netmgt.config.linkd.Package thisPackage : m_config.getPackageCollection()) { final String n = thisPackage.getName(); if (n != null && n.equals(name)) { return thisPackage; } } } finally { getReadLock().unlock(); } return null; } /** {@inheritDoc} */ public List<InetAddress> getIpList(final Package pkg) { getReadLock().lock(); try { if (pkg == null) return null; final Filter filter = pkg.getFilter(); if (filter == null) return null; final StringBuffer filterRules = new StringBuffer(filter.getContent()); LogUtils.debugf(this, "getIpList: package is %s. filter rules are: %s", pkg.getName(), filterRules.toString()); return FilterDaoFactory.getInstance().getActiveIPAddressList(filterRules.toString()); } finally { getReadLock().unlock(); } } /** {@inheritDoc} */ public String getIpRouteClassName(final String sysoid) { getReadLock().lock(); try { for (final String oidMask : m_oidMask2IpRouteclassName.keySet()) { if (sysoid.startsWith(oidMask)) { return m_oidMask2IpRouteclassName.get(oidMask); } } } finally { getReadLock().unlock(); } return DEFAULT_IP_ROUTE_CLASS_NAME; } /** {@inheritDoc} */ public String getVlanClassName(final String sysoid) { getReadLock().lock(); try { for (final String oidMask : m_oidMask2VlanclassName.keySet()) { if (sysoid.startsWith(oidMask)) { return m_oidMask2VlanclassName.get(oidMask); } } } finally { getReadLock().unlock(); } return null; } /** * {@inheritDoc} * * Returns the first package that the IP belongs to, null if none. * * <strong>Note: </strong>Evaluation of the interface against a package * filter will only work if the IP is already in the database. */ public org.opennms.netmgt.config.linkd.Package getFirstPackageMatch(final InetAddress ipaddr) { getReadLock().lock(); try { for (final org.opennms.netmgt.config.linkd.Package pkg : m_config.getPackageCollection()) { if (isInterfaceInPackage(ipaddr, pkg)) { return pkg; } } } finally { getReadLock().unlock(); } return null; } /** * {@inheritDoc} * * Returns a list of package names that the IP belongs to, null if none. * * <strong>Note: </strong>Evaluation of the interface against a package * filter will only work if the IP is already in the database. */ public List<String> getAllPackageMatches(final InetAddress ipaddr) { final List<String> matchingPkgs = new ArrayList<String>(); getReadLock().lock(); try { for (final org.opennms.netmgt.config.linkd.Package pkg : m_config.getPackageCollection()) { final String pkgName = pkg.getName(); if (isInterfaceInPackage(ipaddr, pkg)) { matchingPkgs.add(pkgName); } } } finally { getReadLock().unlock(); } return matchingPkgs; } /** {@inheritDoc} */ public boolean hasClassName(final String sysoid) { getReadLock().lock(); try { for (final String oidMask : m_oidMask2VlanclassName.keySet()) { if (sysoid.startsWith(oidMask)) { return true; } } } finally { getReadLock().unlock(); } return false; } /** {@inheritDoc} */ public String getDefaultIpRouteClassName() { return DEFAULT_IP_ROUTE_CLASS_NAME; } /** * <p>update</p> * * @throws java.io.IOException if any. * @throws org.exolab.castor.xml.MarshalException if any. * @throws org.exolab.castor.xml.ValidationException if any. */ public abstract void update() throws IOException, MarshalException, ValidationException; /** * This method is used to establish package against IP list mapping, with * which, the IP list is selected per package via the configured filter rules * from the database. */ public void updatePackageIpListMap() { getWriteLock().lock(); try { for (final org.opennms.netmgt.config.linkd.Package pkg : m_config.getPackageCollection()) { // // Get a list of IP addresses per package against the filter rules from // database and populate the package, IP list map. // try { final List<InetAddress> ipList = getIpList(pkg); LogUtils.tracef(this, "createPackageIpMap: package %s: ipList size = %d", pkg.getName(), ipList.size()); if (ipList != null && ipList.size() > 0) { m_pkgIpMap.put(pkg, ipList); } } catch (final Throwable t) { LogUtils.errorf(this, t, "createPackageIpMap: failed to map package: %s to an IP list", pkg.getName()); } } } finally { getWriteLock().unlock(); } } /** * <p>useIpRouteDiscovery</p> * * @return a boolean. */ public boolean useIpRouteDiscovery() { if (m_config.hasUseIpRouteDiscovery()) return m_config.getUseIpRouteDiscovery(); return true; } /** * <p>saveRouteTable</p> * * @return a boolean. */ public boolean saveRouteTable() { if (m_config.hasSaveRouteTable()) return m_config.getSaveRouteTable(); return true; } /** * <p>useCdpDiscovery</p> * * @return a boolean. */ public boolean useCdpDiscovery() { if (m_config.hasUseCdpDiscovery()) return m_config.getUseCdpDiscovery(); return true; } /** * <p>useBridgeDiscovery</p> * * @return a boolean. */ public boolean useBridgeDiscovery() { if (m_config.hasUseBridgeDiscovery()) return m_config.getUseBridgeDiscovery(); return true; } /** * <p>saveStpNodeTable</p> * * @return a boolean. */ public boolean saveStpNodeTable() { if (m_config.hasSaveStpNodeTable()) return m_config.getSaveStpNodeTable(); return true; } /** * <p>enableDiscoveryDownload</p> * * @return a boolean. */ public boolean enableDiscoveryDownload() { if (m_config.hasEnableDiscoveryDownload()) return m_config.getEnableDiscoveryDownload(); return false; } /** * <p>saveStpInterfaceTable</p> * * @return a boolean. */ public boolean saveStpInterfaceTable() { if (m_config.hasSaveStpInterfaceTable()) return m_config.getSaveStpInterfaceTable(); return true; } public long getInitialSleepTime() { if (m_config.hasInitial_sleep_time()) return m_config.getInitial_sleep_time(); return 1800000; } public long getSnmpPollInterval() { if (m_config.hasSnmp_poll_interval()) return m_config.getSnmp_poll_interval(); return 900000; } public long getDiscoveryLinkInterval() { if (m_config.hasSnmp_poll_interval()) return m_config.getDiscovery_link_interval(); return 3600000; } /** * <p>getThreads</p> * * @return a int. */ public int getThreads() { if (m_config.hasThreads()) return m_config.getThreads(); return 5; } /** {@inheritDoc} */ public boolean hasIpRouteClassName(final String sysoid) { for (final String oidMask : m_oidMask2IpRouteclassName.keySet()) { if (sysoid.startsWith(oidMask)) { return true; } } return false; } private void updateUrlIpMap() { for (final org.opennms.netmgt.config.linkd.Package pkg : m_config.getPackageCollection()) { if (pkg == null) continue; for (final String urlname : pkg.getIncludeUrlCollection()) { final java.util.List<String> iplist = IpListFromUrl.parse(urlname); if (iplist.size() > 0) { m_urlIPMap.put(urlname, iplist); } } } } private void initializeIpRouteClassNames() throws IOException, MarshalException, ValidationException { getWriteLock().lock(); try { final Iproutes iproutes = m_config.getIproutes(); if (iproutes == null) { LogUtils.infof(this, "no iproutes found in config"); return; } for (final Vendor vendor : iproutes.getVendorCollection()) { final SnmpObjectId curRootSysOid = new SnmpObjectId(vendor.getSysoidRootMask()); final String curClassName = vendor.getClassName(); for (final String specific : vendor.getSpecific()) { final SnmpObjectId oidMask = new SnmpObjectId(specific); oidMask.prepend(curRootSysOid); m_oidMask2IpRouteclassName.put(oidMask.toString(), curClassName); LogUtils.debugf(this, "initializeIpRouteClassNames: adding class %s for oid %s", curClassName, oidMask.toString()); } } } finally { getWriteLock().unlock(); } } private void initializeVlanClassNames() throws IOException, MarshalException, ValidationException { getWriteLock().lock(); try { final Vlans vlans = m_config.getVlans(); if (vlans == null) { LogUtils.infof(this, "initializeVlanClassNames: no vlans found in config"); } final List<String> excludedOids = new ArrayList<String>(); for (final Vendor vendor : vlans.getVendorCollection()) { final SnmpObjectId curRootSysOid = new SnmpObjectId(vendor.getSysoidRootMask()); final String curClassName = vendor.getClassName(); for (final String specific : vendor.getSpecific()) { final SnmpObjectId oidMask = new SnmpObjectId(specific); oidMask.prepend(curRootSysOid); m_oidMask2VlanclassName.put(oidMask.toString(), curClassName); LogUtils.debugf(this, "initializeVlanClassNames: adding class %s for oid %s", curClassName, oidMask.toString()); } for (final ExcludeRange excludeRange : vendor.getExcludeRangeCollection()) { final SnmpObjectId snmpBeginOid = new SnmpObjectId(excludeRange.getBegin()); final SnmpObjectId snmpEndOid = new SnmpObjectId(excludeRange.getEnd()); final SnmpObjectId snmpRootOid = getRootOid(snmpBeginOid); if (snmpBeginOid.getLength() == snmpEndOid.getLength() && snmpRootOid.isRootOf(snmpEndOid)) { final SnmpObjectId snmpCurOid = new SnmpObjectId(snmpBeginOid); while (snmpCurOid.compare(snmpEndOid) <= 0) { excludedOids.add(snmpCurOid.toString()); LogUtils.debugf(this, "initializeVlanClassNames: signing excluded class %s for oid %s", curClassName, curRootSysOid.toString().concat(snmpCurOid.toString())); int lastCurCipher = snmpCurOid.getLastIdentifier(); lastCurCipher++; int[] identifiers = snmpCurOid.getIdentifiers(); identifiers[identifiers.length - 1] = lastCurCipher; snmpCurOid.setIdentifiers(identifiers); } } } for (final IncludeRange includeRange : vendor.getIncludeRangeCollection()) { final SnmpObjectId snmpBeginOid = new SnmpObjectId(includeRange.getBegin()); final SnmpObjectId snmpEndOid = new SnmpObjectId(includeRange.getEnd()); final SnmpObjectId rootOid = getRootOid(snmpBeginOid); if (snmpBeginOid.getLength() == snmpEndOid.getLength() && rootOid.isRootOf(snmpEndOid)) { final SnmpObjectId snmpCurOid = new SnmpObjectId(snmpBeginOid); while (snmpCurOid.compare(snmpEndOid) <= 0) { if (!excludedOids.contains(snmpBeginOid.toString())) { final SnmpObjectId oidMask = new SnmpObjectId(snmpBeginOid); oidMask.prepend(curRootSysOid); m_oidMask2VlanclassName.put(oidMask.toString(), curClassName); LogUtils.debugf(this, "initializeVlanClassNames: adding class %s for oid %s", curClassName, oidMask.toString()); } int lastCipher = snmpBeginOid.getLastIdentifier(); lastCipher++; int[] identifiers = snmpBeginOid.getIdentifiers(); identifiers[identifiers.length - 1] = lastCipher; snmpCurOid.setIdentifiers(identifiers); } } } } } finally { getWriteLock().unlock(); } } private SnmpObjectId getRootOid(final SnmpObjectId snmpObj) { getReadLock().lock(); try { final int[] identifiers = snmpObj.getIdentifiers(); final int[] rootIdentifiers = new int[identifiers.length - 1]; for (int i = 0; i < identifiers.length - 1; i++) { rootIdentifiers[i] = identifiers[i]; } return new SnmpObjectId(rootIdentifiers); } finally { getReadLock().unlock(); } } /** * This method is used to determine if the named interface is included in * the passed package's URL includes. If the interface is found in any of * the URL files, then a value of true is returned, else a false value is * returned. * * <pre> * * The file URL is read and each entry in this file checked. Each line * in the URL file can be one of - * <IP><space>#<comments> * or * <IP> * or * #<comments> * * Lines starting with a '#' are ignored and so are characters after * a '<space>#' in a line. * * </pre> * * @param addr * The interface to test against the package's URL * @param url * The URL file to read * * @return True if the interface is included in the URL, false otherwise. */ private boolean isInterfaceInUrl(final InetAddress addr, final String url) { getReadLock().lock(); try { // get list of IPs in this URL final List<String> iplist = m_urlIPMap.get(url); if (iplist != null && iplist.size() > 0) { return iplist.contains(InetAddressUtils.str(addr)); } } finally { getReadLock().unlock(); } return false; } /** * <p>reloadXML</p> * * @param reader a {@link java.io.Reader} object. * @throws org.exolab.castor.xml.MarshalException if any. * @throws org.exolab.castor.xml.ValidationException if any. * @throws java.io.IOException if any. */ @Deprecated protected void reloadXML(final Reader reader) throws MarshalException, ValidationException, IOException { getWriteLock().lock(); try { m_config = CastorUtils.unmarshal(LinkdConfiguration.class, reader); updateUrlIpMap(); updatePackageIpListMap(); initializeVlanClassNames(); initializeIpRouteClassNames(); } finally { getWriteLock().unlock(); } } /** * <p>reloadXML</p> * * @param stream a {@link java.io.InputStream} object. * @throws org.exolab.castor.xml.MarshalException if any. * @throws org.exolab.castor.xml.ValidationException if any. * @throws java.io.IOException if any. */ protected void reloadXML(final InputStream stream) throws MarshalException, ValidationException, IOException { getWriteLock().lock(); try { m_config = CastorUtils.unmarshal(LinkdConfiguration.class, stream); updateUrlIpMap(); updatePackageIpListMap(); initializeVlanClassNames(); initializeIpRouteClassNames(); } finally { getWriteLock().unlock(); } } /** * Saves the current in-memory configuration to disk and reloads * * @throws org.exolab.castor.xml.MarshalException if any. * @throws java.io.IOException if any. * @throws org.exolab.castor.xml.ValidationException if any. */ public void save() throws MarshalException, IOException, ValidationException { getWriteLock().lock(); try { // marshall to a string first, then write the string to the file. This // way the original config isn't lost if the xml from the marshall is hosed. final StringWriter stringWriter = new StringWriter(); Marshaller.marshal(m_config, stringWriter); saveXml(stringWriter.toString()); update(); } finally { getWriteLock().unlock(); } } /** * <p>saveXml</p> * * @param xml a {@link java.lang.String} object. * @throws java.io.IOException if any. */ protected abstract void saveXml(final String xml) throws IOException; public boolean forceIpRouteDiscoveryOnEthernet() { if (m_config.hasForceIpRouteDiscoveryOnEthernet()) return m_config.getForceIpRouteDiscoveryOnEthernet(); return false; } }