| 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; |
| } |
| } |