package net.onrc.onos.apps.proxyarp; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import net.floodlightcontroller.util.MACAddress; import net.onrc.onos.core.datastore.KVArpCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implements a basic ARP cache which maps IPv4 addresses to MAC addresses. * Mappings time out after a short period of time (currently 1 min). We don't * try and refresh the mapping before the entry times out because as a * controller we don't know if the mapping is still needed. */ class ArpCache { private static final Logger log = LoggerFactory.getLogger(ArpCache.class); private static long arpEntryTimeoutConfig = 60000; // ms (1 min) private final KVArpCache kvArpCache; // Protected by locking on the ArpCache object (this) private final ConcurrentMap<InetAddress, ArpCacheEntry> arpCache; /** * Represents a MAC address entry with a timestamp in the ARP cache. * ARP cache entries are considered invalid if their timestamp is older * than a timeout value. */ private static class ArpCacheEntry { private final MACAddress macAddress; private long timeLastSeen; /** * Class constructor, specifying the MAC address for the entry. * * @param macAddress MAC address for the entry */ public ArpCacheEntry(MACAddress macAddress) { this.macAddress = macAddress; this.timeLastSeen = System.currentTimeMillis(); } /** * Returns the MAC address this entry represents. * * @return this entry's MAC address */ public MACAddress getMacAddress() { return macAddress; } /** * Update the timestamp for this entry. * * @param time the new timestamp to update the entry with */ public void setTimeLastSeen(long time) { timeLastSeen = time; } /** * Returns whether the entry has timed out or not. * * @return true if the entry has timed out. */ public boolean isExpired() { return System.currentTimeMillis() - timeLastSeen > arpEntryTimeoutConfig; } } /** * Class constructors. */ public ArpCache() { arpCache = new ConcurrentHashMap<InetAddress, ArpCacheEntry>(); kvArpCache = new KVArpCache(); } public void setArpEntryTimeoutConfig(long arpEntryTimeout) { if (arpEntryTimeout <= 0) { throw new IllegalArgumentException(Long.toString(arpEntryTimeout)); } arpEntryTimeoutConfig(arpEntryTimeout); } private static void arpEntryTimeoutConfig(long arpEntryTimeout) { ArpCache.arpEntryTimeoutConfig = arpEntryTimeout; log.debug("Set arpEntryTimeoutConfig {}", ArpCache.arpEntryTimeoutConfig); } public long getArpEntryTimeout() { return ArpCache.arpEntryTimeoutConfig; } /** * Get the MAC address that is mapped to an IP address in the ARP cache. * * @param ipAddress the IP address to look up * @return the MAC address if found in the cache, null if not */ synchronized MACAddress lookup(InetAddress ipAddress) { ArpCacheEntry arpEntry = arpCache.get(ipAddress); if (arpEntry == null) { return null; } if (arpEntry.isExpired()) { // Entry has timed out so we'll remove it and return null log.trace("Removing expired ARP entry for {}", ipAddress.getHostAddress()); arpCache.remove(ipAddress); return null; } return arpEntry.getMacAddress(); } /** * Update an entry in the ARP cache. If the IP to MAC mapping is already * in the cache, its timestamp will be updated. If not, the entry will * be added with a new timestamp of the current time. * * @param ipAddress the IP address that will be mapped in the cache * @param macAddress the MAC address that maps to {@code ipAddress} */ synchronized void update(InetAddress ipAddress, MACAddress macAddress) { ArpCacheEntry arpEntry = arpCache.get(ipAddress); if (arpEntry != null && arpEntry.getMacAddress().equals(macAddress)) { arpEntry.setTimeLastSeen(System.currentTimeMillis()); log.debug("The same ArpCache, ip {}, mac {}. " + "Update local cache last seen time only.", ipAddress, macAddress); } else { arpCache.put(ipAddress, new ArpCacheEntry(macAddress)); kvArpCache.forceCreate(ipAddress, macAddress.toBytes()); log.debug("Create/Update ip {}, mac {} in ArpCache.", ipAddress, macAddress); } } /** * Remove an entry in the ARP cache. * * @param ipAddress the IP address that will be mapped in the cache */ synchronized void remove(InetAddress ipAddress) { ArpCacheEntry entry = arpCache.remove(ipAddress); if (entry == null) { log.debug("ArpCache doesn't have the ip key {}.", ipAddress); } else { kvArpCache.forceDelete(ipAddress); log.debug("Remove it in ArpCache and DB, ip {}", ipAddress); } } /** * Retrieve a list of all mappings in the ARP cache. * * @return list of all ARP mappings, formatted as a human-readable string */ List<String> getMappings() { List<String> result = new ArrayList<String>(arpCache.size()); for (Map.Entry<InetAddress, ArpCacheEntry> entry : arpCache.entrySet()) { result.add(entry.getKey().getHostAddress() + " => " + entry.getValue().getMacAddress().toString() + (entry.getValue().isExpired() ? " : EXPIRED" : " : VALID")); } return result; } /** * Retrieve a list of all expired IPs in the ARP cache. * * @return list of all expired IPs */ List<InetAddress> getExpiredArpCacheIps() { List<InetAddress> result = new ArrayList<InetAddress>(); for (Entry<InetAddress, ArpCacheEntry> entry : arpCache.entrySet()) { if (entry.getValue().isExpired()) { log.debug("add to the expired ip list, ip {}", entry.getKey()); result.add(entry.getKey()); } } return result; } }