/* * Copyright (c) 2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * 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.sdnplatform.devicemanager.internal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.openflow.protocol.OFPhysicalPort; import org.openflow.util.HexString; import org.sdnplatform.core.IHAListener; import org.sdnplatform.core.IOFSwitch; import org.sdnplatform.core.IControllerService.Role; import org.sdnplatform.core.annotations.LogMessageCategory; import org.sdnplatform.core.annotations.LogMessageDoc; import org.sdnplatform.core.annotations.LogMessageDocs; import org.sdnplatform.core.module.ModuleContext; import org.sdnplatform.core.module.IModule; import org.sdnplatform.core.module.IPlatformService; import org.sdnplatform.devicemanager.IDevice; import org.sdnplatform.devicemanager.IEntityClass; import org.sdnplatform.devicemanager.IEntityClassifierService; import org.sdnplatform.devicemanager.SwitchPort; import org.sdnplatform.packet.Ethernet; import org.sdnplatform.packet.IPv4; import org.sdnplatform.storage.IResultSet; import org.sdnplatform.storage.IStorageSourceListener; import org.sdnplatform.storage.IStorageSourceService; import org.sdnplatform.storage.StorageException; import org.sdnplatform.tagmanager.ITagListener; import org.sdnplatform.tagmanager.ITagManagerService; import org.sdnplatform.tagmanager.Tag; import org.sdnplatform.tagmanager.TagDoesNotExistException; import org.sdnplatform.tagmanager.TagInvalidHostMacException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @LogMessageCategory("Device Management") public class BetterDeviceManagerImpl extends DeviceManagerImpl implements IModule, ITagManagerService, IStorageSourceListener, IHAListener { protected static Logger logger = LoggerFactory.getLogger(BetterDeviceManagerImpl.class); // Additional dependencies protected IEntityClassifierService ecs; /******** * TagNamespace */ protected static final String DEFAULT_NAMESPACE = "default"; /******** * Tag table fields */ public static final String TAGMAPPING_TABLE_NAME = "controller_tagmapping"; public static final String TAGMAPPING_ID_COLUMN_NAME = "id"; public static final String TAGMAPPING_TAG_COLUMN_NAME = "tag_id"; public static final String TAGMAPPING_MAC_COLUMN_NAME = "mac"; public static final String TAGMAPPING_VLAN_COLUMN_NAME = "vlan"; public static final String TAGMAPPING_SWITCH_COLUMN_NAME = "dpid"; public static final String TAGMAPPING_INTERFACE_COLUMN_NAME = "ifname"; public static final String TAG_TABLE_NAME = "controller_tag"; public static final String TAG_ID_COLUMN_NAME = "id"; public static final String TAG_NAMESPACE_COLUMN_NAME = "namespace"; public static final String TAG_NAME_COLUMN_NAME = "name"; public static final String TAG_VALUE_COLUMN_NAME = "value"; public static final String TAG_PERSIST_COLUMN_NAME = "persist"; /* * spoofing protection tables and columns */ protected static final String COMPOUND_KEY_SEPARATOR_REGEX = "\\|"; protected static final String HOSTCONFIG_TABLE_NAME = "controller_hostconfig"; protected static final String HOSTCONFIG_ID_COLUMN_NAME = "id"; protected static final String HOSTCONFIG_ADDRESSPACE_COLUMN_NAME = "address_space"; protected static final String HOSTCONFIG_VLAN_COLUMN_NAME = "vlan"; protected static final String HOSTCONFIG_MAC_COLUMN_NAME = "mac"; protected static final String HOSTSECURITYIPADDRESS_TABLE_NAME = "controller_hostsecurityipaddress"; protected static final String HOSTSECURITYIPADDRESS_ID_COLUMN_NAME = "id"; protected static final String HOSTSECURITYIPADDRESS_HOSTCONFIG_COLUMN_NAME = "hostconfig_id"; protected static final String HOSTSECURITYIPADDRESS_IP_COLUMN_NAME = "ip"; protected static final String HOSTSECURITYATTACHMENTPOINT_TABLE_NAME = "controller_hostsecurityattachmentpoint"; protected static final String HOSTSECURITYATTACHMENTPOINT_ID_COLUMN_NAME = "id"; protected static final String HOSTSECURITYATTACHMENTPOINT_HOSTCONFIG_COLUMN_NAME = "hostconfig_id"; protected static final String HOSTSECURITYATTACHMENTPOINT_DPID_COLUMN_NAME = "dpid"; protected static final String HOSTSECURITYATTACHMENTPOINT_IF_NAME_REGEX_COLUMN_NAME = "if_name_regex"; public static class DeviceId { String addressSpace; Short vlan; Long mac; public DeviceId(String addressSpace, Short vlan, Long mac) { super(); this.addressSpace = addressSpace; this.vlan = vlan; this.mac = mac; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((addressSpace == null) ? 0 : addressSpace.hashCode()); result = prime * result + ((mac == null) ? 0 : mac.hashCode()); result = prime * result + ((vlan == null) ? 0 : vlan.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DeviceId other = (DeviceId) obj; if (addressSpace == null) { if (other.addressSpace != null) return false; } else if (!addressSpace.equals(other.addressSpace)) return false; if (mac == null) { if (other.mac != null) return false; } else if (!mac.equals(other.mac)) return false; if (vlan == null) { if (other.vlan != null) return false; } else if (!vlan.equals(other.vlan)) return false; return true; } @Override public String toString() { return "DeviceId [addressSpace=" + addressSpace + ", vlan=" + vlan + ", mac=" + mac + "]"; } } public static class ScopedIp { public String addressSpace; public Integer ip; public ScopedIp(String addressSpace, Integer ip) { super(); this.addressSpace = addressSpace; this.ip = ip; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((addressSpace == null) ? 0 : addressSpace.hashCode()); result = prime * result + ((ip == null) ? 0 : ip.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ScopedIp other = (ScopedIp) obj; if (addressSpace == null) { if (other.addressSpace != null) return false; } else if (!addressSpace.equals(other.addressSpace)) return false; if (ip == null) { if (other.ip != null) return false; } else if (!ip.equals(other.ip)) return false; return true; } @Override public String toString() { return "ScopedIp [addressSpace=" + addressSpace + ", ip=" + ip + "]"; } } /** * This is the switch dpid, iface name tuple */ public class SwitchInterface { Long dpid; String ifaceName; /** * @param dpid2 * @param ifaceName2 */ public SwitchInterface(Long dpid2, String ifaceName2) { this.dpid = dpid2; this.ifaceName = ifaceName2; } public SwitchInterface(SwitchInterface other) { this.dpid = other.dpid; this.ifaceName = other.ifaceName; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((dpid == null) ? 0 : dpid.hashCode()); result = prime * result + ((ifaceName == null) ? 0 : ifaceName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SwitchInterface other = (SwitchInterface) obj; if (dpid == null) { if (other.dpid != null) return false; } else if (!dpid.equals(other.dpid)) return false; if (ifaceName == null) { if (other.ifaceName != null) return false; } else if (!ifaceName.equals(other.ifaceName)) return false; return true; } public Short getPortNumber() { IOFSwitch sw = controllerProvider.getSwitches().get(dpid); if (sw == null) return null; OFPhysicalPort p = sw.getPort(ifaceName); if (p == null) return null; return p.getPortNumber(); } } /******** * Tag States and lock */ protected ReentrantReadWriteLock m_lock; private Map<String, ConcurrentHashMap<String, Set<Tag>>> m_tags; private Map<String, Set<EntityConfig>> tagToEntities; private Map<EntityConfig, Set<Tag>> entityToTags; /******** * Tag notification */ private Set<ITagListener> m_listeners; /* * Spoofing protection */ /** * We synchronize on this object when we change our internal anti-spoofing * structures. We don't need to synchronize on reads since we use * ConcurrentHashMaps. Of course, we could also update our data structures * lock-free but updating is not on the critical path so the coding and * complexity overhead isn't worth it. */ protected Object antiSpoofingConfigWriteLock; protected SwitchInterfaceRegexMatcher interfaceRegexMatcher; /** * Anti-spoofing configuration: per IP-address (scoped to an address-space): * the set of devices/hosts that are allowed to use this IP */ protected ConcurrentHashMap<ScopedIp,Collection<DeviceId>> hostSecurityIpMap; /** * NOTE: CURRENTLY UNUSED SINCE WE FUNNEL EVERYTHING THROUGH REGEX * MATCHING. * Anti-spoofing configuration: per address-space: * the mapping from MAC address to switch port. * First key: address space name * Second key: Mac address * Data: collection of switch-port names. */ protected ConcurrentHashMap<String, ConcurrentHashMap<Long, Collection<SwitchInterface>>> hostSecurityInterfaceMap; /** * Anti-spoofing configuration: the mapping from host (deviceId) * to the key (tag?) that interfaceRegexMatcher will use when querying for * switch interfaces matching this host * Key: deviceId * Data: collection of keys to query */ protected ConcurrentHashMap<DeviceId, Collection<String>> hostSecurityInterfaceRegexMap; @Override protected Device learnDeviceByEntity(Entity entity) { return super.learnDeviceByEntity(entity); } //*************** // ITagManagerService //*************** @Override public Set<String> getNamespaces() { return new HashSet<String>(m_tags.keySet()); } /** * Create a new tag. * @param tag */ @Override public Tag createTag(String ns, String name, String value) { return new Tag(ns, name, value, true); } /** * Create a new tag, with persist. * @param tag */ @Override public Tag createTag(String ns, String name, String value, boolean persist) { return new Tag(ns, name, value, persist); } /** * Add a new tag. The tag is saved in DB. * @param tag */ @Override public void addTag(Tag tag) { if (tag == null) return; String ns = DEFAULT_NAMESPACE; if (!tag.getNamespace().equals("")) { ns = tag.getNamespace(); } Map<String, Object> rowValues = new HashMap<String, Object>(); rowValues.put(TAG_ID_COLUMN_NAME, getTagKey(ns, tag.getName(), tag.getValue())); rowValues.put(TAG_NAMESPACE_COLUMN_NAME, ns); rowValues.put(TAG_NAME_COLUMN_NAME, tag.getName()); rowValues.put(TAG_VALUE_COLUMN_NAME, tag.getValue()); rowValues.put(TAG_PERSIST_COLUMN_NAME, tag.getPersist()); storageSource.insertRow(TAG_TABLE_NAME, rowValues); } /** * Delete a tag. The tag is removed from DB. * @param tag */ @Override public void deleteTag(Tag tag) throws TagDoesNotExistException { if (tag == null) return; String ns = DEFAULT_NAMESPACE; if (!tag.getNamespace().equals("")) { ns = tag.getNamespace(); } String tagDBKey = getTagKey(ns, tag.getName(), tag.getValue()); if (m_tags.get(ns) == null || !m_tags.get(ns).containsKey(tag.getName())) { throw new TagDoesNotExistException(tag.toString()); } storageSource.deleteRow(TAG_TABLE_NAME, tagDBKey); } /** * Map a tag to a host. The mapping is saved in DB. * @param tag * @param hostmac */ @Override public void mapTagToHost(Tag tag, String hostmac, Short vlan, String dpid, String interfaceName) throws TagDoesNotExistException, TagInvalidHostMacException { if (tag == null) throw new TagDoesNotExistException("null tag"); if (hostmac != null && !Ethernet.isMACAddress(hostmac)) { throw new TagInvalidHostMacException(hostmac + " is an invalid mac address"); } String ns = DEFAULT_NAMESPACE; if (!tag.getNamespace().equals("")) { ns = tag.getNamespace(); } if (m_tags.get(ns) == null || !m_tags.get(ns).containsKey(tag.getName())) { throw new TagDoesNotExistException(tag.toString()); } if (hostmac == null && vlan == null && dpid == null && interfaceName == null) return; String blankStr = new String(""); String vlanStr = blankStr; if (vlan != null) { vlanStr = new String(vlan.toString()); } if (dpid == null && interfaceName != null) return; if (hostmac == null) hostmac = blankStr; if (dpid == null) dpid = blankStr; if (interfaceName == null) interfaceName = blankStr; Map<String, Object> rowValues = new HashMap<String, Object>(); String tagid = getTagKey(ns, tag.getName(), tag.getValue()); String id = tagid + Tag.KEY_SEPARATOR + hostmac + Tag.KEY_SEPARATOR + vlanStr + Tag.KEY_SEPARATOR + dpid + Tag.KEY_SEPARATOR + interfaceName; rowValues.put(TAGMAPPING_ID_COLUMN_NAME, id); rowValues.put(TAGMAPPING_TAG_COLUMN_NAME, tagid); rowValues.put(TAGMAPPING_MAC_COLUMN_NAME, hostmac); rowValues.put(TAGMAPPING_VLAN_COLUMN_NAME, vlanStr); rowValues.put(TAGMAPPING_SWITCH_COLUMN_NAME, dpid); rowValues.put(TAGMAPPING_INTERFACE_COLUMN_NAME, interfaceName); storageSource.insertRowAsync(TAGMAPPING_TABLE_NAME, rowValues); } /** * UnMap a tag from a host. The mapping is removed from DB. * @param tag * @param hostmac */ @Override public void unmapTagToHost(Tag tag, String hostmac, Short vlan, String dpid, String interfaceName) throws TagDoesNotExistException, TagInvalidHostMacException { if (tag == null) throw new TagDoesNotExistException("null tag"); if (hostmac != null && !Ethernet.isMACAddress(hostmac)) { throw new TagInvalidHostMacException(hostmac + " is an invalid mac address"); } if (hostmac == null && vlan == null && dpid == null && interfaceName == null) return; String blankStr = new String(""); String vlanStr = blankStr; if (vlan != null) vlanStr = new String(vlan.toString()); if (hostmac == null) hostmac = blankStr; if (dpid == null && interfaceName != null) return; if (dpid == null) dpid = blankStr; if (interfaceName == null) interfaceName = blankStr; String ns = DEFAULT_NAMESPACE; if (!tag.getNamespace().equals("")) { ns = tag.getNamespace(); } if (m_tags.get(ns) == null || !m_tags.get(ns).containsKey(tag.getName())) { throw new TagDoesNotExistException(tag.toString()); } String id = getTagKey(ns, tag.getName(), tag.getValue()) + Tag.KEY_SEPARATOR + hostmac + Tag.KEY_SEPARATOR + vlanStr + Tag.KEY_SEPARATOR + dpid + Tag.KEY_SEPARATOR + interfaceName; storageSource.deleteRowAsync(TAGMAPPING_TABLE_NAME, id); } @Override public Set<Tag> getTags(String ns, String name) { if (ns != null && ns.equals("")) { ns = DEFAULT_NAMESPACE; } Map<String, Set<Tag>> nsTags = m_tags.get(ns); if (nsTags != null) { return nsTags.get(name); } return null; } @Override public Set<Tag> getTagsByNamespace(String ns) { if (ns == null) return null; Set<Tag> tags = new HashSet<Tag>(); if (ns.equals("")) { ns = DEFAULT_NAMESPACE; } ConcurrentHashMap<String, Set<Tag>> nsTags = m_tags.get(ns); if (nsTags != null) { Iterator<Map.Entry<String, Set<Tag>>> it = nsTags.entrySet().iterator(); while (it.hasNext()) { tags.addAll(it.next().getValue()); } return tags; } else { return null; } } @Override public void addListener(ITagListener listener) { m_listeners.add(listener); } @Override public void removeListener(ITagListener listener) { m_listeners.remove(listener); } @Override public Set<Tag> getTagsByDevice(IDevice device) { logger.debug("Getting tags for device-" + device); Set <Tag> tagsForEntities = new HashSet<Tag>(); Set <Entity> allPartialEntities = new HashSet <Entity>(); allPartialEntities.add(new Entity(device.getMACAddress(), null, null, null, null, null)); for (Short vlan : device.getVlanId()) { allPartialEntities.add(new Entity(0, vlan, null, null, null, null)); allPartialEntities.add(new Entity(device.getMACAddress(), vlan, null, null, null, null)); for (SwitchPort switchPort : device.getAttachmentPoints(true)) { allPartialEntities.add(new Entity(device.getMACAddress(), vlan, null, switchPort.getSwitchDPID(), null, null)); allPartialEntities.add(new Entity(device.getMACAddress(), vlan, null, switchPort.getSwitchDPID(), switchPort.getPort(), null)); allPartialEntities.add(new Entity(0, vlan, null, switchPort.getSwitchDPID(), null, null)); allPartialEntities.add(new Entity(0, vlan, null, switchPort.getSwitchDPID(), switchPort.getPort(), null)); } } for (SwitchPort switchPort : device.getAttachmentPoints(true)) { allPartialEntities.add(new Entity(0, null, null, switchPort.getSwitchDPID(), null, null)); allPartialEntities.add(new Entity(0, null, null, switchPort.getSwitchDPID(), switchPort.getPort(), null)); allPartialEntities.add(new Entity(device.getMACAddress(), null, null, switchPort.getSwitchDPID(), null, null)); allPartialEntities.add(new Entity(device.getMACAddress(), null, null, switchPort.getSwitchDPID(), switchPort.getPort(), null)); } for (Entity thisEntity : allPartialEntities) { tagsForEntities.addAll(this.getTagsByEntityConfig( EntityConfig.convertEntityToEntityConfig( controllerProvider, thisEntity))); } return tagsForEntities; } /* (non-Javadoc) * @see org.sdnplatform.tagmanager.ITagManagerService# * getTagsByHost(java.lang.String, java.lang.Short, java.lang.String, * java.lang.String) */ @Override public Set<Tag> getTagsByHost(String hostmac, Short vlan, String dpid, String interfaceName) { if (hostmac == null && vlan == null && dpid == null && interfaceName == null) return new HashSet<Tag>(); String vlanStr = null; if (vlan != null) vlanStr = vlan.toString(); return this.getTagsByEntityConfig( new EntityConfig(hostmac, vlanStr, dpid, interfaceName)); } /* (non-Javadoc) * @see org.sdnplatform.tagmanager.ITagManagerService# * getDevicesByTag(org.sdnplatform.tagmanager.Tag) */ @Override public Set <IDevice> getDevicesByTag(Tag tag) { String tagDBKey = tag.getDBKey(); Set<EntityConfig> entities = this.tagToEntities.get(tagDBKey); if (entities == null) return null; Set <IDevice> retDevices= new HashSet <IDevice>(); for (EntityConfig entity : entities) { Short vlan = (entity.vlan == null ? null : new Short(entity.vlan)); Long dpid = (entity.dpid == null ? null : HexString.toLong(entity.dpid)); Iterator<? extends IDevice> devicesReMapped = this.queryDevices(HexString.toLong(entity.mac), vlan, null, dpid, extractSwitchPortNumber(entity.dpid, entity.interfaceName)); while (devicesReMapped.hasNext()) { retDevices.add(devicesReMapped.next()); } } return retDevices; } public void removeAllListeners() { m_listeners.clear(); } @Override public Tag getTagFromDBId(String id) { if (id == null) return null; String[] fields = id.split("\\"+ Tag.KEY_SEPARATOR); // Tag key is expected to be in namespace|id format. if (fields == null || fields.length != 3) { return null; } return new Tag(fields[0], fields[1], fields[2]); } //**************************** // Internal Methods = Tag related //***************************** private Integer extractSwitchPortNumber(String dpid, String ifaceName) { if (dpid != null && ifaceName != null) { IOFSwitch sw = controllerProvider.getSwitches().get( HexString.toLong(dpid)); if (sw == null) { logger.info("Switch {} not in switch map", dpid); return null; } OFPhysicalPort p = sw.getPort(ifaceName); if (p == null) { logger.info("On Switch {} Port {} does not exist yet", dpid, ifaceName); return null; } return (int)p.getPortNumber(); } return null; } /** * finds the change set of devices that got remapped and then notify * @param thisEntity */ private void notifyAllDevicesReMapped(EntityConfig thisEntity) { /* query for all devices that match this entity and then * notify the tag listeners of this change */ Integer port = null; if (thisEntity.dpid != null && thisEntity.interfaceName != null) { port = extractSwitchPortNumber(thisEntity.dpid, thisEntity.interfaceName); } Long longDpid = null; if (thisEntity.dpid != null) longDpid = new Long(HexString.toLong(thisEntity.dpid)); Long longMac = null; if (thisEntity.mac != null) longMac = new Long(HexString.toLong(thisEntity.mac)); Short vlan = null; if (thisEntity.vlan != null) vlan = new Short(thisEntity.vlan); Iterator<Device> devicesReMapped = this.getDeviceIteratorForQuery(longMac, vlan, null, longDpid, port); ArrayList <Device> devicesToNotify = new ArrayList <Device>(); while (devicesReMapped.hasNext()) { Device device = devicesReMapped.next(); if (reclassifyDevice(device) == false) { devicesToNotify.add(device); } } if (devicesToNotify.isEmpty()) return; Iterator<? extends IDevice> devicesReMappedToNotify = devicesToNotify.iterator(); TagManagerNotification notification = new TagManagerNotification(devicesReMappedToNotify); notification.setAction( TagManagerNotification.Action.TAGDEVICES_REMAPPED); this.notifyListeners(notification); } private void addEntityTagConfig(EntityConfig thisEntity, Tag thisTag) { Set <Tag> thisEntityTags = entityToTags.get(thisEntity); if (thisEntityTags == null) { thisEntityTags = new HashSet <Tag>(); entityToTags.put(thisEntity, thisEntityTags); } if (thisEntityTags.add(thisTag) == true) { /* notify listeners of this change * */ notifyAllDevicesReMapped(thisEntity); } Set <EntityConfig> thisTagEntities = tagToEntities.get(thisTag.getDBKey()); if (thisTagEntities == null) { thisTagEntities = new HashSet<EntityConfig>(); tagToEntities.put(thisTag.getDBKey(), thisTagEntities); } thisTagEntities.add(thisEntity); } private void removeEntityTagConfig(EntityConfig thisEntity, Tag thisTag) { Set <Tag> thisEntityTags = entityToTags.get(thisEntity); if (thisEntityTags != null) { if (thisEntityTags.contains(thisTag)) { if (thisEntityTags.remove(thisTag) == true) { /* notify listeners of this change * */ notifyAllDevicesReMapped(thisEntity); } } if (thisEntityTags.isEmpty()) { entityToTags.remove(thisEntity); } } Set <EntityConfig> thisTagEntities = tagToEntities.get(thisTag.getDBKey()); if (thisTagEntities != null) { thisTagEntities.remove(thisEntity); if (thisTagEntities.isEmpty()) { tagToEntities.remove(thisTag.getDBKey()); } } } protected Set<Tag> getTagsByEntityConfig(EntityConfig thisEntity) { if (thisEntity == null) return new HashSet<Tag>(); if (logger.isTraceEnabled()) { logger.trace("get Tags for entity - " + thisEntity.toString()); for (EntityConfig entity: this.entityToTags.keySet()) { logger.trace("Found a entityConfig key in entityToTags - " + entity.toString()); } } Set<Tag> tags = this.entityToTags.get(thisEntity); if (tags == null) { return new HashSet<Tag>(); } if (logger.isTraceEnabled()) { for (Tag tag : tags) { logger.debug("getTagsByEntityConfig: Tag value is - " + tag.getDBKey()); } } return tags; } protected Set<EntityConfig> getEntityConfigsByTag(Tag tag) { if (tag == null) return null; Set<EntityConfig> entities = this.tagToEntities.get(tag.getDBKey()); if (entities == null) { return new HashSet<EntityConfig>(); } return entities; } @LogMessageDoc(level="ERROR", message="Exception caught handling tagManager notification", explanation="A transient error occurred while notifying tog changes", recommendation=LogMessageDoc.TRANSIENT_CONDITION) @SuppressWarnings("incomplete-switch") private void notifyListeners(TagManagerNotification notification) { if (m_listeners != null) { for (ITagListener listener : m_listeners) { try { switch (notification.getAction()) { case ADD_TAG: listener.tagAdded(notification.getTag()); break; case DELETE_TAG: listener.tagDeleted(notification.getTag()); break; case TAGDEVICES_REMAPPED: logger.debug("Notifying listeners that the tags of" + " devices for remapped"); listener.tagDevicesReMapped( notification.getDevices()); break; } } catch (Exception e) { logger.error("Exception caught handling tagManager notification", e); } } } } /** * Add a new tag without updating storage. * Return true if no tag is associated with the id; * false if a tag is associated with the id and is updated. * @param tag */ private boolean addTagInternal(Tag tag) { boolean retCode = true; String ns = tag.getNamespace(); if (ns.equals("")) { ns = DEFAULT_NAMESPACE; } ConcurrentHashMap<String, Set<Tag>> nsTags = m_tags.get(ns); if (nsTags == null) { nsTags = new ConcurrentHashMap<String, Set<Tag>>(); m_tags.put(ns, nsTags); } Set<Tag> tags = nsTags.get(tag.getName()); if (tags == null) { tags = new CopyOnWriteArraySet<Tag>(); Set<Tag> oldtags = nsTags.putIfAbsent(tag.getName(), tags); if (oldtags != null) tags = oldtags; } retCode = tags.add(tag); return retCode; } /** * Delete a new tag without updating storage * @param tag */ private boolean deleteTagInternal(Tag tag) { boolean retCode = true; String ns = tag.getNamespace(); if (ns.equals("")) { ns = DEFAULT_NAMESPACE; } ConcurrentHashMap<String, Set<Tag>> nsTags = m_tags.get(ns); if (nsTags != null) { Set<Tag> tags = nsTags.get(tag.getName()); if (tags == null) { retCode = false; } else { retCode = tags.remove(tag); if (tags.size() == 0) nsTags.remove(tag.getName()); if (nsTags.size() == 0) m_tags.remove(ns); } } else { retCode = false; } // Remove tag mapping when the tag is removed if (retCode) { Set<EntityConfig> entities = this.tagToEntities.remove(tag.getDBKey()); if (entities != null) { for (EntityConfig thisEntity : entities) { this.removeEntityTagConfig(thisEntity, tag); } } } return retCode; } /** * @param newTag * @param mac * @param vlan * @param dpid * @param interfaceName */ private void addTagHostMappingInternal(Tag tag, String mac, String vlan, String dpid, String interfaceName) { addTagInternal(tag); EntityConfig entity = new EntityConfig(mac, vlan, dpid, interfaceName); this.addEntityTagConfig(entity, tag); } private void deleteTagHostMappingInternal(Tag tag, String mac, String vlan, String dpid, String interfaceName) { EntityConfig entity = new EntityConfig(mac, vlan, dpid, interfaceName); this.removeEntityTagConfig(entity, tag); return; } //********************* // Storage Listener //********************* public IStorageSourceService getStorageSource() { return this.storageSource; } public void setStorageSource(IStorageSourceService s) { storageSource = s; } /** * Called when a new row is inserted into a table. * * @param tableName The table where the rows were inserted * @param rowKeys The keys of the rows that were inserted */ @LogMessageDoc(level="WARN", message="BigDeviceManager ignore rowModified event for table {table}", explanation="Ignored modify event for unknown table", recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) @Override public void rowsModified(String tableName, Set<Object> rowKeys) { if (tableName == null || rowKeys == null) { return; } if (tableName.equals(TAG_TABLE_NAME)) { tagRowsInserted(rowKeys); } else if (tableName.equals(TAGMAPPING_TABLE_NAME)) { tagMappingRowsInserted(rowKeys); } else if (tableName.equals(HOSTSECURITYIPADDRESS_TABLE_NAME)) { hostSecurityIpAddressModified(rowKeys, false); } else if (tableName.equals(HOSTSECURITYATTACHMENTPOINT_TABLE_NAME)) { hostSecurityAttachmentPointModified(rowKeys, false); } else { logger.warn("BigDeviceManager ignore rowModified event for table {}", tableName); } } /** * Called when a new row is deleted from a table. * * @param tableName The table where the rows were deleted * @param rowKeys The keys of the rows that were deleted */ @LogMessageDoc(level="WARN", message="BigDeviceManager ignore rowDeleted event for table {table}", explanation="Ignored delete event for unknown table", recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) @Override public void rowsDeleted(String tableName, Set<Object> rowKeys) { if (tableName == null || rowKeys == null) { return; } if (tableName.equals(TAG_TABLE_NAME)) { tagRowsDeleted(rowKeys); } else if (tableName.equals(TAGMAPPING_TABLE_NAME)) { tagMappingRowsDeleted(rowKeys); } else if (tableName.equals(HOSTSECURITYIPADDRESS_TABLE_NAME)) { hostSecurityIpAddressModified(rowKeys, true); } else if (tableName.equals(HOSTSECURITYATTACHMENTPOINT_TABLE_NAME)) { hostSecurityAttachmentPointModified(rowKeys, true); } else { logger.warn("BigDeviceManager ignore rowDeleted event for table {}", tableName); } } //********************* // Internal Methods - Storage/Tag related //********************* private void tagRowsInserted(Set<Object> rowKeys) { try { m_lock.writeLock().lock(); for (Object row: rowKeys) { String key = ""; if (!(row instanceof String)) { continue; } else { key = (String)row; } IResultSet resultSet = storageSource.getRow(TAG_TABLE_NAME, key); if (resultSet.next()) { String ns = null; String tagName = null; String tagValue = null; ns = resultSet.getString(TAG_NAMESPACE_COLUMN_NAME); tagName = resultSet.getString(TAG_NAME_COLUMN_NAME); tagValue = resultSet.getString(TAG_VALUE_COLUMN_NAME); Tag newTag = new Tag(ns, tagName, tagValue); TagManagerNotification.Action action = TagManagerNotification.Action.ADD_TAG; if (addTagInternal(newTag)) { TagManagerNotification notification = new TagManagerNotification(newTag, null, action); notifyListeners(notification); } } } } finally { m_lock.writeLock().unlock(); } } @LogMessageDoc(level="ERROR", message="Configuration error, entity spec has a interface " + "specified but no dpid, rejecting this config {tag} {mac}" + " {vlan} {OFportname}", explanation="Tag match configuration has a OF-Portname specified " + "but switch dpid not specifed", recommendation=LogMessageDoc.GENERIC_ACTION) private void tagMappingRowsInserted(Set<Object> rowKeys) { try { m_lock.writeLock().lock(); for (Object row: rowKeys) { String key = ""; if (!(row instanceof String)) { continue; } else { key = (String)row; } IResultSet resultSet = storageSource.getRow(TAGMAPPING_TABLE_NAME, key); if (resultSet.next()) { String tagid = resultSet.getString(TAGMAPPING_TAG_COLUMN_NAME); String mac = resultSet.getString(TAGMAPPING_MAC_COLUMN_NAME); String vlanStr = resultSet.getString(TAGMAPPING_VLAN_COLUMN_NAME); String dpid = resultSet.getString(TAGMAPPING_SWITCH_COLUMN_NAME); String interfaceName = resultSet.getString(TAGMAPPING_INTERFACE_COLUMN_NAME); Tag newTag = getTagFromDBId(tagid); if (dpid == null && interfaceName != null) { logger.error("Configuration error, entity spec has a " + "interface specified but no dpid, rejecting " + "this config - " + tagid + " " + mac + " " + vlanStr + " " + interfaceName); return; } this.addTagHostMappingInternal(newTag, mac, vlanStr, dpid, interfaceName); } } } finally { m_lock.writeLock().unlock(); } } private void tagRowsDeleted (Set<Object> rowKeys) { try { m_lock.writeLock().lock(); for (Object row: rowKeys) { String key = null; if (row instanceof String) { key = (String)row; Tag tag = getTagFromDBId(key); if (!deleteTagInternal(tag)) { logger.info("rowsDeleted, tag {} doesn't exist", tag); return; } TagManagerNotification notification = new TagManagerNotification(tag, null, TagManagerNotification.Action.DELETE_TAG); notifyListeners(notification); } } } finally { m_lock.writeLock().unlock(); } } private void tagMappingRowsDeleted (Set<Object> rowKeys) { try { m_lock.writeLock().lock(); for (Object row: rowKeys) { String key = ""; if (!(row instanceof String)) { continue; } else { key = (String)row; } String[] fields = key.split("\\"+ Tag.KEY_SEPARATOR); Tag newTag = null; String mac = null; String vlanStr = null; String dpid = null; String interfaceName = null; if (fields == null || fields.length < 3) { return; } newTag = new Tag(fields[0], fields[1], fields[2]); if (fields.length < 3) return; if (!fields[3].isEmpty()) mac = fields[3]; if (fields.length > 4) { if (!fields[4].isEmpty()) vlanStr = fields[4]; if (fields.length > 5) { if (!fields[5].isEmpty()) dpid = fields[5]; if (fields.length > 6) if (!fields[6].isEmpty()) interfaceName = fields[6]; } } if (dpid == null && interfaceName != null) { logger.error("Configuration error, entity spec has a " + "interface specified but no dpid, rejecting " + "this config - " + newTag + " " + mac + " " + vlanStr + " " + interfaceName); return; } this.deleteTagHostMappingInternal(newTag, mac, vlanStr, dpid, interfaceName); } } finally { m_lock.writeLock().unlock(); } } private String getTagKey(String ns, String name, String value) { return ns + Tag.KEY_SEPARATOR + name + Tag.KEY_SEPARATOR + value; } protected Tag getTagFromMappingId(String id) { if (id == null) return null; String[] fields = id.split("\\"+ Tag.KEY_SEPARATOR); // Tag key is expected to be in namespace|id format. if (fields == null || fields.length != 4) { return null; } return new Tag(fields[0], fields[1], fields[2]); } /** * Clears the in-memory tags */ protected void clearTagsFromMemory() { m_lock.writeLock().lock(); try { m_tags.clear(); this.tagToEntities.clear(); this.entityToTags.clear(); } finally { m_lock.writeLock().unlock(); } } /** * Read the Tag information from storage */ protected void loadTagsFromStorage() throws StorageException { m_lock.writeLock().lock(); try { // Flush device mappings m_tags.clear(); this.tagToEntities.clear(); this.entityToTags.clear(); IResultSet resultSet = storageSource.executeQuery(TAG_TABLE_NAME, new String[]{TAG_ID_COLUMN_NAME, TAG_NAMESPACE_COLUMN_NAME, TAG_NAME_COLUMN_NAME, TAG_VALUE_COLUMN_NAME}, null, null); while (resultSet.next()) { String ns = resultSet.getString(TAG_NAMESPACE_COLUMN_NAME); String tagName = resultSet.getString(TAG_NAME_COLUMN_NAME); String tagValue = resultSet.getString(TAG_VALUE_COLUMN_NAME); Tag tag = new Tag(ns, tagName, tagValue); addTagInternal(tag); logger.trace("Configured {} ", tag); } resultSet = storageSource.executeQuery(TAGMAPPING_TABLE_NAME, null, null, null); while (resultSet.next()) { String tagid = resultSet.getString(TAGMAPPING_TAG_COLUMN_NAME); String mac = resultSet.getString(TAGMAPPING_MAC_COLUMN_NAME); String vlanStr = resultSet.getString( TAGMAPPING_VLAN_COLUMN_NAME); String dpid = resultSet.getString(TAGMAPPING_SWITCH_COLUMN_NAME); String interfaceName = resultSet.getString(TAGMAPPING_INTERFACE_COLUMN_NAME); Tag tag = getTagFromDBId(tagid); if ((dpid == null || dpid.isEmpty()) && (interfaceName != null && !interfaceName.isEmpty())) { logger.error("Configuration error, entity spec has a " + "interface specified but no dpid, rejecting " + "this config - " + tagid + " " + mac + " " + vlanStr + " " + interfaceName); return; } this.addTagHostMappingInternal(tag, mac, vlanStr, dpid, interfaceName); logger.trace("Configured mapping ", tag, " - " + mac + vlanStr + dpid + interfaceName); } } finally { m_lock.writeLock().unlock(); } } //****************************************** // Internal Methods - Security/Spoofing Related //****************************************** /** * Handle a config change from storage: HostSecurityIpAddress config * has changed. * * @param compoundKeys A set of compound row keys from the database. We * will split the compound key in its components instead of * querying the table and all its references * @param isDeleted Inidicates whether the entry should be added or * deleted from our internal structures. */ @LogMessageDoc(level="ERROR", message="RowKey from HostSecurityIpAddress table is not a String", explanation="Error in a Host security IP-Address configuration, " + "compound key is not a string", recommendation=LogMessageDoc.GENERIC_ACTION) protected void hostSecurityIpAddressModified(Set<Object> compoundKeys, boolean isDeleted) { // We don't really need to synchronized here, // hostSecurityIpAddressModified(String,boolean) will also // synchronize but this prevents potentially thousands of lock/unlock // operations synchronized(antiSpoofingConfigWriteLock) { for(Object key: compoundKeys) { if (!(key instanceof String)) { logger.error("RowKey from HostSecurityIpAddress table " + "is not a String"); continue; } hostSecurityIpAddressModified((String)key, isDeleted); } } } /** * Handle a config change from storage: HostSecurityIpAddress config * has changed. * * @param compoundKey A single compound row key from the database. We * will split the compound key in its components instead of * querying the table and all its references * @param isDeleted Inidicates whether the entry should be added or * deleted from our internal structures. */ @LogMessageDocs({ @LogMessageDoc(level="ERROR", message="Cannot specify a VLAN for " + "HostSecurityIpAddress if the address space is " + "default. {Compound-key}", explanation="Unsupported Vlan in a host security Ip-Address " + "configuration for default address space", recommendation=LogMessageDoc.GENERIC_ACTION), @LogMessageDoc(level="ERROR", message="Invalid Vlan in compound key from " + "HostSecurityIpAddress table", explanation="Invalid Vlan in a host security Ip-Address " + "configuration", recommendation=LogMessageDoc.GENERIC_ACTION), @LogMessageDoc(level="ERROR", message="Invalid MAC address in compound key from " + "HostSecurityIpAddress table", explanation="Invalid Vlan in a host security Ip-Address " + "configuration", recommendation=LogMessageDoc.GENERIC_ACTION), @LogMessageDoc(level="ERROR", message="Invalid IP address in compound key from " + "HostSecurityIpAddress table", explanation="Invalid Vlan in a host security Ip-Address " + "configuration", recommendation=LogMessageDoc.GENERIC_ACTION), @LogMessageDoc(level="ERROR", message="Invalid compound primary key in " + "HostSecurityIpAddress table", explanation="Invalid compound primary key in a host " + "security Ip-Address configuration", recommendation=LogMessageDoc.GENERIC_ACTION), }) protected void hostSecurityIpAddressModified(String compoundKey, boolean isDeleted) { String[] fields = compoundKey.split(COMPOUND_KEY_SEPARATOR_REGEX); if (fields.length != 4) { logger.error("Invalid compound primary key {} in " + "HostSecurityIpAddress table", compoundKey); return; } String addrSpace = fields[0]; String vlanString = fields[1]; String macString = fields[2]; String ipString = fields[3]; Short vlan; if (vlanString.equals("")) { vlan = null; } else if (! addrSpace.equals("default")) { // not default address space but vlan specified: invalid logger.error("Cannot specify a VLAN for " + "HostSecurityIpAddress if the address space is " + "not default. Compound-key: {}", compoundKey); return; } else { try { vlan = Short.parseShort(vlanString); } catch (NumberFormatException e) { logger.error("Invalid Vlan {} in compound key {} from " + "HostSecurityIpAddress table", vlanString, compoundKey); return; } if (vlan < 1 || vlan > 4095) { logger.error("Invalid VLAN {} in compound key {} from " + "HostSecurityIpAddress table", vlanString, compoundKey); return; } } Long mac; try { mac = HexString.toLong(macString); } catch (NumberFormatException e) { logger.error("Invalid MAC address {} in compound key {} from " + "HostSecurityIpAddress table", macString, compoundKey); return; } if (mac >= (1L<<48)) { logger.error("Invalid MAC address {} in compound key {} from " + "HostSecurityIpAddress table", macString, compoundKey); return; } Integer ip; try { ip = IPv4.toIPv4Address(ipString); } catch (IllegalArgumentException e) { logger.error("Invalid IP address {} in compound key {} from " + "HostSecurityIpAddress table", ipString, compoundKey); return; } if (logger.isDebugEnabled()) { String verb = (isDeleted) ? "Removing" : "Adding"; logger.debug("HostSecurityIp: {} entry addressSpace={}, mac={}, ip={}", new Object[] { verb, addrSpace, macString, ipString }); } synchronized(antiSpoofingConfigWriteLock) { if (isDeleted) removeAntiSpoofingIp2Mac(addrSpace, vlan, mac, ip); else addAntiSpoofingIp2Mac(addrSpace, vlan, mac, ip); } } /** * Add an IP to MAC anti-spoofing entry. Lock MAC to IP * * NOTE: The caller needs to hold the anti-spoofing write lock. * @param addrSpace * @param vlan * @param mac * @param ip */ protected void addAntiSpoofingIp2Mac(String addrSpace, Short vlan, Long mac, Integer ip) { ScopedIp scopedIp = new ScopedIp(addrSpace, ip); DeviceId host = new DeviceId(addrSpace, vlan, mac); Collection<DeviceId> hosts = hostSecurityIpMap.get(scopedIp); if (hosts == null) { hosts = Collections.newSetFromMap( new ConcurrentHashMap<DeviceId, Boolean>()); } hosts.add(host); hostSecurityIpMap.putIfAbsent(scopedIp, hosts); } /** * Remove an IP to MAC anti-spoofing entry. * * NOTE: The caller needs to hold the anti-spoofing write lock. * @param addrSpace * @param vlan * @param mac * @param ip */ protected void removeAntiSpoofingIp2Mac(String addrSpace, Short vlan, Long mac, Integer ip) { ScopedIp scopedIp = new ScopedIp(addrSpace, ip); Collection<DeviceId> hosts = hostSecurityIpMap.get(scopedIp); if (hosts == null) { return; } DeviceId host = new DeviceId(addrSpace, vlan, mac); hosts.remove(host); if (hosts.isEmpty()) hostSecurityIpMap.remove(scopedIp); } @LogMessageDoc(level="ERROR", message="RowKey from HostSecurityAttachmentPoint table is not a " + "String", explanation="Error in a Host security attachment-point " + "configuration, compound key is not a string", recommendation=LogMessageDoc.GENERIC_ACTION) /** * Handle a config change from storage: HostSecurityAttachmentPoint config * has changed. * * @param compoundKeys A set of compound row keys from the database. We * will split the compound key in its components instead of * querying the table and all its references * @param isDeleted Indicates whether the entry should be added or * deleted from our internal structures. */ protected void hostSecurityAttachmentPointModified(Set<Object> compoundKeys, boolean isDeleted) { // We don't really need to synchronized here, // hostSecurityAttachmentPointModified(String,boolean) will also // synchronize but this prevents potentially thousands of lock/unlock // operations synchronized(antiSpoofingConfigWriteLock) { for(Object key: compoundKeys) { if (!(key instanceof String)) { logger.error("RowKey from HostSecurityAttachmentPoint table " + "is not a String"); continue; } hostSecurityAttachmentPointModified((String)key, isDeleted); } } } @LogMessageDocs({ @LogMessageDoc(level="ERROR", message="Cannot specify a VLAN for " + "HostSecurityAttachmentPoint if the address space is " + "default. {Compound-key}", explanation="Unsupported Vlan in a host security Ip-Address " + "configuration for default address space", recommendation=LogMessageDoc.GENERIC_ACTION), @LogMessageDoc(level="ERROR", message="Invalid MAC in compound key from " + "HostSecurityAttachmentPoint table", explanation="Invalid MAC in a host security Ip-Address " + "configuration", recommendation=LogMessageDoc.GENERIC_ACTION), @LogMessageDoc(level="ERROR", message="Invalid Vlan in compound key from " + "HostSecurityAttachmentPoint table", explanation="Invalid Vlan in a host security Ip-Address " + "configuration", recommendation=LogMessageDoc.GENERIC_ACTION), @LogMessageDoc(level="ERROR", message="Invalid DPID in compound key from " + "HostSecurityAttachmentPoint table", explanation="Invalid DPID in a host security Ip-Address " + "configuration", recommendation=LogMessageDoc.GENERIC_ACTION), @LogMessageDoc(level="ERROR", message="Invalid compound primary key in " + "HostSecurityAttachmentPoint table", explanation="Invalid compound primary key in a host " + "security attachment point configuration", recommendation=LogMessageDoc.GENERIC_ACTION) }) /** * Handle a config change from storage: HostSecurityAttachmentPoint config * has changed. * * @param compoundKey A single compound row key from the database. We * will split the compound key in its components instead of * querying the table and all its references * @param isDeleted Inidicates whether the entry should be added or * deleted from our internal structures. */ protected void hostSecurityAttachmentPointModified(String compoundKey, boolean isDeleted) { String[] fields = compoundKey.split(COMPOUND_KEY_SEPARATOR_REGEX); if (fields.length != 5) { logger.error("Invalid compound primary key {} in " + "HostSecurityAttachmentPoint table", compoundKey); return; } String addrSpace = fields[0]; String vlanString = fields[1]; String macString = fields[2]; String dpidString = fields[3]; String ifaceNameRegex = fields[4]; Short vlan; if (vlanString.equals("")) { vlan = null; } else if (! addrSpace.equals("default")) { // not default address space but vlan specified: invalid logger.error("Cannot specify a VLAN for " + "HostSecurityAttachmentPoint if the address space is " + "not default. Compound-key: {}", compoundKey); return; } else { try { vlan = Short.parseShort(vlanString); } catch (NumberFormatException e) { logger.error("Invalid Vlan {} in compound key {} from " + "HostSecurityAttachmentPoint table", vlanString, compoundKey); return; } if (vlan < 1 || vlan > 4095) { logger.error("Invalid VLAN {} in compound key {} from " + "HostSecurityAttachmentPoint table", vlanString, compoundKey); return; } } Long mac; try { mac = HexString.toLong(macString); } catch (NumberFormatException e) { logger.error("Invalid MAC address {} in compound key {} from " + "HostSecurityAttachmentPoint table", macString, compoundKey); return; } if (mac >= (1L<<48)) { logger.error("Invalid MAC address {} in compound key {} from " + "HostSecurityAttachmentPoint table", macString, compoundKey); return; } Long dpid; try { if (dpidString.equals("\010")) // octal code for backspace, used by sdncon for NULL dpid = null; else dpid = HexString.toLong(dpidString); } catch (NumberFormatException e) { logger.error("Invalid DPID {} in compound key {} from " + "HostSecurityAttachmentPoint table", dpidString, compoundKey); return; } if (logger.isDebugEnabled()) { String verb = (isDeleted) ? "Removing" : "Adding"; logger.debug("HostSecurityAttachmentPoint: {} entry addressSpace={}, mac={}, " + "switch={}, ifaceRegex={}", new Object[] { verb, addrSpace, macString, dpidString, ifaceNameRegex }); } synchronized(antiSpoofingConfigWriteLock) { if (isDeleted) removeHostSecurityInterfaceEntry(addrSpace, vlan, mac, dpid, ifaceNameRegex); else addHostSecurityInterfaceEntry(addrSpace, vlan, mac, dpid, ifaceNameRegex); } } /** * Add an host to SwitchInterface anti-spoofing entry. * * The caller needs to hold the anti-spoofing write lock. * @param addrSpace * @param vlan * @param mac * @param dpid * @param ifaceName */ protected void addHostSecurityInterfaceEntry(String addrSpace, Short vlan, Long mac, Long dpid, String ifaceName) { DeviceId host = new DeviceId(addrSpace, vlan, mac); Collection<String> keys = hostSecurityInterfaceRegexMap.get(host); if (keys == null) { keys = Collections.newSetFromMap( new ConcurrentHashMap<String, Boolean>()); } String key = addrSpace + "|" + vlan + "|" + mac + "|" + dpid + "|" + ifaceName; keys.add(key); hostSecurityInterfaceRegexMap.put(host, keys); interfaceRegexMatcher.addOrUpdate(key, dpid, ifaceName); /* CURRENTLY UNUSED: * use for exact matches SwitchInterface switchIface = new SwitchInterface(dpid, ifaceName); ConcurrentHashMap<Long, Collection<SwitchInterface>> mac2switchIface = hostSecurityInterfaceMap.get(addrSpace); if (mac2switchIface == null) { mac2switchIface = new ConcurrentHashMap<Long, Collection<SwitchInterface>>(); } Collection<SwitchInterface> ifaces = mac2switchIface.get(mac); if (ifaces == null) { ifaces = Collections.newSetFromMap( new ConcurrentHashMap<SwitchInterface, Boolean>()); } ifaces.add(switchIface); mac2switchIface.putIfAbsent(mac, ifaces); hostSecurityInterfaceMap.putIfAbsent(addrSpace, mac2switchIface); */ } /** * Remove an host to SwitchInterface anti-spoofing entry. * * The caller needs to hold the anti-spoofing write lock. * @param addrSpace * @param vlan * @param mac * @param dpid * @param ifaceName */ protected void removeHostSecurityInterfaceEntry(String addrSpace, Short vlan, Long mac, Long dpid, String ifaceName) { DeviceId host = new DeviceId(addrSpace, vlan, mac); Collection<String> keys = hostSecurityInterfaceRegexMap.get(host); if (keys == null) { return; } String key = addrSpace + "|" + vlan + "|" + mac + "|" + dpid + "|" + ifaceName; keys.remove(key); if (keys.isEmpty()) hostSecurityInterfaceRegexMap.remove(host); interfaceRegexMatcher.remove(key); /* CURRENTLY UNUSED: * use for exact matches SwitchInterface switchIface = new SwitchInterface(dpid, ifaceName); ConcurrentHashMap<Long, Collection<SwitchInterface>> mac2switchIface = hostSecurityInterfaceMap.get(addrSpace); if (mac2switchIface == null) { return; } Collection<SwitchInterface> ifaces = mac2switchIface.get(mac); if (ifaces == null) { return; } ifaces.remove(switchIface); if (ifaces.isEmpty()) mac2switchIface.remove(mac); if (mac2switchIface.isEmpty()) hostSecurityInterfaceMap.remove(addrSpace); */ } /** * Read anti-spoofing configuration from storage */ protected void readAntiSpoofingConfigFromStorage() throws StorageException { synchronized(antiSpoofingConfigWriteLock) { hostSecurityIpMap.clear(); hostSecurityInterfaceMap.clear(); IResultSet resultSet; // Read HostSecurityIpAddress resultSet = storageSource .executeQuery(HOSTSECURITYIPADDRESS_TABLE_NAME, null, null, null); while (resultSet.next()) { hostSecurityIpAddressModified( resultSet.getString(HOSTSECURITYIPADDRESS_ID_COLUMN_NAME), false); } // Read HostSecurityAttachmentPoint resultSet = storageSource .executeQuery(HOSTSECURITYATTACHMENTPOINT_TABLE_NAME, null, null, null); while (resultSet.next()) { hostSecurityAttachmentPointModified( resultSet.getString(HOSTSECURITYATTACHMENTPOINT_ID_COLUMN_NAME), false); } } } /* * allows for exact matching of switchInterfaces. CURRENTLY UNUSED SINCE * WE USE ONLY THE REGEX MATCHER FOR THE TIME BEING. */ protected boolean checkHostSecurityInterfaceExact(String addrSpace, Entity entity) { Long mac = entity.getMacAddress(); Map<Long, Collection<SwitchInterface>> mac2SwitchPort = hostSecurityInterfaceMap.get(addrSpace); if (mac2SwitchPort == null) { // No config for this address space: allow return true; } Collection<SwitchInterface> switchInterfaces = mac2SwitchPort.get(mac); if (switchInterfaces == null || switchInterfaces.isEmpty()) { // no config for this Mac: allow return true; } for(SwitchInterface swi: switchInterfaces) { if (swi.getPortNumber() != null && topology.isConsistent(entity.getSwitchDPID(), (short)entity.getSwitchPort().intValue(), swi.dpid, swi.getPortNumber())) { // the port in the entity is consistent with one of the allowed // ports: allow the entity. We note that a port going into // a BD is consistent with every other port going to the same // BD. return true; } } return false; } @LogMessageDoc(level="WARN", message="Drop packet with srcMac equals to {virtualMac} " + "for {services}", explanation="Dropped packet with Source MAC equal to virtual mac of" + " service", recommendation=LogMessageDoc.GENERIC_ACTION) @Override protected boolean isEntityAllowed(Entity entity, IEntityClass entityClass) { String addressSpaceName = entityClass.getName(); Integer ip = entity.getIpv4Address(); Long mac = entity.getMacAddress(); Short vlan = null; if (entityClass.getKeyFields().contains(DeviceField.VLAN)) vlan = entity.getVlan(); DeviceId host = new DeviceId(addressSpaceName, vlan, mac); // First, check for IP spoofing if (entity.getIpv4Address() != null) { ScopedIp scopedIp = new ScopedIp(addressSpaceName, ip); Collection<DeviceId> hosts = hostSecurityIpMap.get(scopedIp); if (hosts != null && (!hosts.contains(host))) return false; } // check attachment point if (!isValidAttachmentPoint(entity.getSwitchDPID(), entity.getSwitchPort())) { // not an AP port: allow return true; } Collection<String> keys = hostSecurityInterfaceRegexMap.get(host); if (keys == null) { // no config for this host: allow return true; } for (String key: keys) { Collection<SwitchPort> ifaces = interfaceRegexMatcher.getInterfacesByKey(key); if (ifaces == null) { continue; } for (SwitchPort swp: ifaces) { if (topology.isAttachmentPointPort(swp.getSwitchDPID(), (short)swp.getPort()) && topology.isConsistent(entity.getSwitchDPID(), (short)entity.getSwitchPort().intValue(), swp.getSwitchDPID(), (short)swp.getPort())) { // the port in the entity is consistent with one of the allowed // ports: allow the entity. We note that a port going into // a BD is consistent with every other port going to the same // BD. return true; } } } return false; } //*************** // IModule //*************** @Override public Collection<Class<? extends IPlatformService>> getModuleServices() { Collection<Class<? extends IPlatformService>> l = new ArrayList<Class<? extends IPlatformService>>(); l.add(ITagManagerService.class); l.addAll(super.getModuleServices()); return l; } @Override public Map<Class<? extends IPlatformService>, IPlatformService> getServiceImpls() { Map<Class<? extends IPlatformService>, IPlatformService> m = super.getServiceImpls(); m.put(ITagManagerService.class, this); return m; } @Override public Collection<Class<? extends IPlatformService>> getModuleDependencies() { return null; } @Override public void init(ModuleContext context) { super.init(context); ecs = context.getServiceImpl(IEntityClassifierService.class); m_listeners = new CopyOnWriteArraySet<ITagListener>(); m_lock = new ReentrantReadWriteLock(); m_tags = new ConcurrentHashMap<String, ConcurrentHashMap<String, Set<Tag>>>(); tagToEntities = new ConcurrentHashMap<String, Set<EntityConfig>>(); entityToTags = new ConcurrentHashMap<EntityConfig, Set<Tag>>(); antiSpoofingConfigWriteLock = new Object(); interfaceRegexMatcher = new SwitchInterfaceRegexMatcher(controllerProvider); hostSecurityIpMap = new ConcurrentHashMap<ScopedIp, Collection<DeviceId>>(); hostSecurityInterfaceMap = new ConcurrentHashMap<String, ConcurrentHashMap<Long,Collection<SwitchInterface>>>(); hostSecurityInterfaceRegexMap = new ConcurrentHashMap<DeviceId, Collection<String>>(); } @Override public void startUp(ModuleContext context) { // Our 'constructor' super.startUp(context); storageSource.createTable(BetterDeviceManagerImpl.TAG_TABLE_NAME, null); storageSource.setTablePrimaryKeyName(BetterDeviceManagerImpl.TAG_TABLE_NAME, BetterDeviceManagerImpl.TAG_ID_COLUMN_NAME); storageSource.createTable(BetterDeviceManagerImpl.TAGMAPPING_TABLE_NAME, null); storageSource.setTablePrimaryKeyName( BetterDeviceManagerImpl.TAGMAPPING_TABLE_NAME, BetterDeviceManagerImpl.TAGMAPPING_ID_COLUMN_NAME); loadTagsFromStorage(); storageSource.addListener(TAG_TABLE_NAME, this); storageSource.addListener(TAGMAPPING_TABLE_NAME, this); // Anti-spoofing storage storageSource.createTable(HOSTSECURITYATTACHMENTPOINT_TABLE_NAME, null); storageSource .setTablePrimaryKeyName(HOSTSECURITYATTACHMENTPOINT_TABLE_NAME, HOSTCONFIG_ID_COLUMN_NAME); storageSource.createTable(HOSTSECURITYIPADDRESS_TABLE_NAME, null); storageSource .setTablePrimaryKeyName(HOSTSECURITYIPADDRESS_TABLE_NAME, HOSTCONFIG_ID_COLUMN_NAME); readAntiSpoofingConfigFromStorage(); storageSource.addListener(HOSTSECURITYIPADDRESS_TABLE_NAME, this); storageSource.addListener(HOSTSECURITYATTACHMENTPOINT_TABLE_NAME, this); controllerProvider.addOFSwitchListener(interfaceRegexMatcher); } //*************** // IHAListener //*************** @LogMessageDoc(level="WARN", message="Unknown controller role: {role}", explanation="Controller's role seems to a unknown role", recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) @Override public void roleChanged(Role oldRole, Role newRole) { super.roleChanged(oldRole, newRole); switch(newRole) { case MASTER: if (oldRole == Role.SLAVE) { logger.debug("Re-reading tags from storage due " + "to HA change from SLAVE->MASTER"); loadTagsFromStorage(); } break; case SLAVE: logger.debug("Clearing tags due to " + "HA change to SLAVE"); clearTagsFromMemory(); break; default: logger.warn("Unknown controller role: {}", newRole); break; } } @Override public void controllerNodeIPsChanged( Map<String, String> curControllerNodeIPs, Map<String, String> addedControllerNodeIPs, Map<String, String> removedControllerNodeIPs) { super.controllerNodeIPsChanged(curControllerNodeIPs, addedControllerNodeIPs, removedControllerNodeIPs); // ignore } }