blob: 39dc119c54f81a13ad1cbd898509cff2a8977bd7 [file] [log] [blame]
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;
}
}