Merge branch 'master' of https://github.com/OPENNETWORKINGLAB/ONOS
diff --git a/conf/cassandra-repair.sh b/conf/cassandra-repair.sh
new file mode 100755
index 0000000..2c2638e
--- /dev/null
+++ b/conf/cassandra-repair.sh
@@ -0,0 +1 @@
+<cassandra_dir>/bin/nodetool repair
diff --git a/conf/logback-deployment.xml b/conf/logback-deployment.xml
new file mode 100644
index 0000000..3877f65
--- /dev/null
+++ b/conf/logback-deployment.xml
@@ -0,0 +1,37 @@
+<configuration scan="true" debug="true">
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%level [%logger:%thread] %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+    <file>./onos-logs/onos.ubuntu.log</file>
+    <encoder>
+      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <appender name="ROLLINGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <file>./onos-logs/onos.ubuntu.log</file>
+    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+      <!-- Roll over to a new log file every day -->
+      <fileNamePattern>./onos-logs/onos.ubuntu.%d{yyyy-MM-dd}.log</fileNamePattern>
+      <!-- Keep 10 days worth of logs -->
+      <maxHistory>10</maxHistory>
+    </rollingPolicy>
+
+    <encoder>
+      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
+    </encoder>
+    
+  </appender>
+
+  <logger name="org" level="WARN"/>
+  <logger name="LogService" level="WARN"/> <!-- Restlet access logging -->
+  <logger name="net.floodlightcontroller.logging" level="WARN"/>
+
+  <root level="DEBUG">
+    <appender-ref ref="ROLLINGFILE" />
+  </root>
+</configuration>
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
index 960d001..a698fd5 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
@@ -40,6 +40,7 @@
 import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
 import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscoveryService;
 import net.onrc.onos.ofcontroller.proxyarp.IArpRequester;
+import net.onrc.onos.ofcontroller.proxyarp.IProxyArpService;
 import net.onrc.onos.ofcontroller.proxyarp.ProxyArpManager;
 import net.onrc.onos.ofcontroller.routing.TopoRouteService;
 import net.onrc.onos.ofcontroller.util.DataPath;
@@ -76,7 +77,8 @@
 
 public class BgpRoute implements IFloodlightModule, IBgpRouteService, 
 									ITopologyListener, IArpRequester,
-									IOFSwitchListener, ILayer3InfoService {
+									IOFSwitchListener, ILayer3InfoService,
+									IProxyArpService {
 	
 	protected static Logger log = LoggerFactory.getLogger(BgpRoute.class);
 
@@ -232,6 +234,7 @@
 		Map<Class<? extends IFloodlightService>, IFloodlightService> m 
 			= new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
 		m.put(IBgpRouteService.class, this);
+		m.put(IProxyArpService.class, this);
 		return m;
 	}
 
@@ -262,7 +265,7 @@
 		
 		//TODO We'll initialise this here for now, but it should really be done as
 		//part of the controller core
-		proxyArp = new ProxyArpManager(floodlightProvider, topology, this);
+		proxyArp = new ProxyArpManager(floodlightProvider, topology, this, restApi);
 		
 		linkUpdates = new ArrayList<LDUpdate>();
 		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
@@ -429,7 +432,7 @@
 		InetAddress dstIpAddress = rib.getNextHop();
 		
 		//See if we know the MAC address of the next hop
-		byte[] nextHopMacAddress = proxyArp.getMacAddress(rib.getNextHop());
+		MACAddress nextHopMacAddress = proxyArp.getMacAddress(rib.getNextHop());
 		
 		//Find the attachment point (egress interface) of the next hop
 		Interface egressInterface = null;
@@ -463,7 +466,7 @@
 				Path path = pushedPaths.get(dstIpAddress);
 				if (path == null) {
 					path = new Path(egressInterface, dstIpAddress);
-					calculateAndPushPath(path, MACAddress.valueOf(nextHopMacAddress));
+					calculateAndPushPath(path, nextHopMacAddress);
 					pushedPaths.put(dstIpAddress, path);
 				}
 				
@@ -476,9 +479,9 @@
 		}
 	}
 	
-	private void addPrefixFlows(Prefix prefix, Interface egressInterface, byte[] nextHopMacAddress) {		
+	private void addPrefixFlows(Prefix prefix, Interface egressInterface, MACAddress nextHopMacAddress) {		
 		log.debug("Adding flows for prefix {} added, next hop mac {}",
-				prefix, HexString.toHexString(nextHopMacAddress));
+				prefix, nextHopMacAddress);
 		
 		//We only need one flow mod per switch, so pick one interface on each switch
 		Map<Long, Interface> srcInterfaces = new HashMap<Long, Interface>();
@@ -533,7 +536,7 @@
 	        
 	        //Set up MAC rewrite action
 	        OFActionDataLayerDestination macRewriteAction = new OFActionDataLayerDestination();
-	        macRewriteAction.setDataLayerAddress(nextHopMacAddress);
+	        macRewriteAction.setDataLayerAddress(nextHopMacAddress.toBytes());
 	        
 	        //Set up output action
 	        OFActionOutput outputAction = new OFActionOutput();
@@ -694,8 +697,8 @@
 			
 			//See if we know the MAC address of the peer. If not we can't
 			//do anything until we learn it
-			byte[] mac = proxyArp.getMacAddress(peer.getIpAddress());
-			if (mac == null) {
+			MACAddress macAddress = proxyArp.getMacAddress(peer.getIpAddress());
+			if (macAddress == null) {
 				log.debug("Don't know MAC for {}", peer.getIpAddress().getHostAddress());
 				//Put in the pending paths list first
 				pathsWaitingOnArp.put(peer.getIpAddress(), path);
@@ -705,7 +708,7 @@
 			}
 			
 			//If we know the MAC, lets go ahead and push the paths to this peer
-			calculateAndPushPath(path, MACAddress.valueOf(mac));
+			calculateAndPushPath(path, macAddress);
 		}
 	}
 	
@@ -959,9 +962,9 @@
 	}
 	
 	@Override
-	public void arpResponse(InetAddress ipAddress, byte[] macAddress) {
-		log.debug("Received ARP response: {} => {}", ipAddress.getHostAddress(), 
-				MACAddress.valueOf(macAddress).toString());
+	public void arpResponse(InetAddress ipAddress, MACAddress macAddress) {
+		log.debug("Received ARP response: {} => {}", 
+				ipAddress.getHostAddress(), macAddress);
 		
 		/*
 		 * We synchronize on this to prevent changes to the ptree while we're pushing
@@ -973,8 +976,7 @@
 			
 			if (path != null) {
 				log.debug("Pushing path to {} at {} on {}", new Object[] {
-						path.getDstIpAddress().getHostAddress(), 
-						MACAddress.valueOf(macAddress),
+						path.getDstIpAddress().getHostAddress(), macAddress,
 						path.getDstInterface().getSwitchPort()});
 				//These paths should always be to BGP peers. Paths to non-peers are
 				//handled once the first prefix is ready to push
@@ -986,7 +988,7 @@
 					}
 				}
 				else {
-					calculateAndPushPath(path, MACAddress.valueOf(macAddress));
+					calculateAndPushPath(path, macAddress);
 					pushedPaths.put(path.getDstIpAddress(), path);
 				}
 			}
@@ -1299,4 +1301,27 @@
 	public MACAddress getRouterMacAddress() {
 		return bgpdMacAddress;
 	}
+
+	/*
+	 * TODO This is a hack to get the REST API to work for ProxyArpManager.
+	 * The REST API is currently tied to the Floodlight module system and we
+	 * need to separate it to allow ONOS modules to use it. For now we will 
+	 * proxy calls through to the ProxyArpManager (which is not a Floodlight 
+	 * module) through this class which is a module.
+	 */
+	@Override
+	public MACAddress getMacAddress(InetAddress ipAddress) {
+		return proxyArp.getMacAddress(ipAddress);
+	}
+
+	@Override
+	public void sendArpRequest(InetAddress ipAddress, IArpRequester requester,
+			boolean retry) {
+		proxyArp.sendArpRequest(ipAddress, requester, retry);		
+	}
+
+	@Override
+	public List<String> getMappings() {
+		return proxyArp.getMappings();
+	}
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpCache.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpCache.java
new file mode 100644
index 0000000..83a3b55
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpCache.java
@@ -0,0 +1,99 @@
+package net.onrc.onos.ofcontroller.proxyarp;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.util.MACAddress;
+
+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.
+ */
+
+/* TODO clean out old ARP entries out of the cache periodically. We currently 
+ * don't do this which means the cache memory size will never decrease. We already
+ * have a periodic thread that can be used to do this in ProxyArpManager.
+ */
+class ArpCache {
+	private final static Logger log = LoggerFactory.getLogger(ArpCache.class);
+	
+	private final static long ARP_ENTRY_TIMEOUT = 60000; //ms (1 min)
+	
+	//Protected by locking on the ArpCache object
+	private final Map<InetAddress, ArpCacheEntry> arpCache;
+	
+	private static class ArpCacheEntry {
+		private final MACAddress macAddress;
+		private long timeLastSeen;	
+
+		public ArpCacheEntry(MACAddress macAddress) {
+			this.macAddress = macAddress;
+			this.timeLastSeen = System.currentTimeMillis();
+		}
+
+		public MACAddress getMacAddress() {
+			return macAddress;
+		}
+		
+		public void setTimeLastSeen(long time){
+			timeLastSeen = time;
+		}
+		
+		public boolean isExpired() {
+			return System.currentTimeMillis() - timeLastSeen > ARP_ENTRY_TIMEOUT;
+		}
+	}
+
+	ArpCache() {
+		arpCache = new HashMap<InetAddress, ArpCacheEntry>();
+	}
+
+	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();
+	}
+
+	synchronized void update(InetAddress ipAddress, MACAddress macAddress){
+		ArpCacheEntry arpEntry = arpCache.get(ipAddress);
+		
+		if (arpEntry != null && arpEntry.getMacAddress().equals(macAddress)){
+			arpEntry.setTimeLastSeen(System.currentTimeMillis());
+		}
+		else {
+			arpCache.put(ipAddress, new ArpCacheEntry(macAddress));
+		}
+	}
+	
+	synchronized 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;
+	}
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpCacheResource.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpCacheResource.java
new file mode 100644
index 0000000..252e66e
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpCacheResource.java
@@ -0,0 +1,18 @@
+package net.onrc.onos.ofcontroller.proxyarp;
+
+import java.util.List;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class ArpCacheResource extends ServerResource {
+
+	@Get("json")
+	public List<String> getArpCache() {
+		IProxyArpService arp = (IProxyArpService) getContext().getAttributes().
+				get(IProxyArpService.class.getCanonicalName());
+		
+		return arp.getMappings();
+	}
+
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpTableEntry.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpTableEntry.java
deleted file mode 100644
index 5830cfd..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpTableEntry.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package net.onrc.onos.ofcontroller.proxyarp;
-
-
-public class ArpTableEntry {
-	
-	private byte[] macAddress;
-	private long timeLastSeen;	
-
-	public ArpTableEntry(byte[] macAddress, long timeLastSeen) {
-		this.macAddress = macAddress;
-		this.timeLastSeen = timeLastSeen;
-	}
-
-	public byte[] getMacAddress() {
-		return macAddress;
-	}
-
-	public long getTimeLastSeen() {
-		return timeLastSeen;
-	}
-	
-	public void setTimeLastSeen(long time){
-		//TODO thread safety issues?
-		timeLastSeen = time;
-	}
-
-}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpWebRoutable.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpWebRoutable.java
new file mode 100644
index 0000000..eefa2db
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpWebRoutable.java
@@ -0,0 +1,22 @@
+package net.onrc.onos.ofcontroller.proxyarp;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+public class ArpWebRoutable implements RestletRoutable {
+
+	@Override
+	public Restlet getRestlet(Context context) {
+		Router router = new Router(context);
+		router.attach("/cache/json", ArpCacheResource.class);
+		return router;
+	}
+
+	@Override
+	public String basePath() {
+		return "/wm/arp";
+	}
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java
deleted file mode 100644
index 1474d02..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package net.onrc.onos.ofcontroller.proxyarp;
-
-import java.net.InetAddress;
-
-import net.floodlightcontroller.packet.ARP;
-
-public class HostArpRequester implements IArpRequester {
-
-	private IProxyArpService arpService;
-	private ARP arpRequest;
-	private long dpid;
-	private short port;
-	
-	public HostArpRequester(IProxyArpService arpService, ARP arpRequest, 
-			long dpid, short port) {
-		
-		this.arpService = arpService;
-		this.arpRequest = arpRequest;
-		this.dpid = dpid;
-		this.port = port;
-	}
-
-	@Override
-	public void arpResponse(InetAddress ipAddress, byte[] macAddress) {
-		arpService.sendArpReply(arpRequest, dpid, port, macAddress);
-	}
-
-}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java
index 90da2ba..66a17a2 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java
@@ -2,6 +2,20 @@
 
 import java.net.InetAddress;
 
+import net.floodlightcontroller.util.MACAddress;
+
+/**
+ * Callback interface for modules using the {@link IProxyArpService} to
+ * send ARP requests.
+ *
+ */
 public interface IArpRequester {
-	public void arpResponse(InetAddress ipAddress, byte[] macAddress);
+	/**
+	 * Callback method that will be called by the {@link IProxyArpService} 
+	 * when it receives a reply for a request previously submitted by this
+	 * {@code IArpRequester}.
+	 * @param ipAddress The IP address than an ARP request was sent for
+	 * @param macAddress The MAC address mapped to the requested IP address
+	 */
+	public void arpResponse(InetAddress ipAddress, MACAddress macAddress);
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java
index 2bb32f4..97844d3 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java
@@ -1,30 +1,20 @@
 package net.onrc.onos.ofcontroller.proxyarp;
 
 import java.net.InetAddress;
+import java.util.List;
 
-import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.util.MACAddress;
 
-public interface IProxyArpService {
-	
-	public final int ARP_REQUEST_TIMEOUT = 2000; //ms
-	
+//Extends IFloodlightService so we can access it from REST API resources
+public interface IProxyArpService extends IFloodlightService{
 	/**
-	 * Tell the IProxyArpService to send an ARP reply with the targetMac to 
-	 * the host on the specified switchport.
-	 * @param arpRequest
-	 * @param dpid
-	 * @param port
-	 * @param targetMac
-	 */
-	public void sendArpReply(ARP arpRequest, long dpid, short port, byte[] targetMac);
-	
-	/**
-	 * Returns the mac address if there is a valid entry in the cache.
+	 * Returns the MAC address if there is a valid entry in the cache.
 	 * Otherwise returns null.
 	 * @param ipAddress
 	 * @return
 	 */
-	public byte[] getMacAddress(InetAddress ipAddress);
+	public MACAddress getMacAddress(InetAddress ipAddress);
 	
 	/**
 	 * Tell the IProxyArpService to send an ARP request for the IP address.
@@ -32,8 +22,13 @@
 	 * @param ipAddress
 	 * @param requester
 	 * @param retry Whether to keep sending requests until the MAC is learnt
-	 * @return
 	 */
 	public void sendArpRequest(InetAddress ipAddress, IArpRequester requester,
 			boolean retry);
+	
+	/**
+	 * Returns a snapshot of the entire ARP cache.
+	 * @return
+	 */
+	public List<String> getMappings();
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
index 6e12f28..31cc4fc 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
@@ -5,7 +5,6 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -20,6 +19,7 @@
 import net.floodlightcontroller.packet.ARP;
 import net.floodlightcontroller.packet.Ethernet;
 import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.restserver.IRestApiService;
 import net.floodlightcontroller.topology.ITopologyService;
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.ofcontroller.bgproute.ILayer3InfoService;
@@ -40,25 +40,25 @@
 import com.google.common.collect.Multimaps;
 import com.google.common.collect.SetMultimap;
 
-//TODO REST API to inspect ARP table
 public class ProxyArpManager implements IProxyArpService, IOFMessageListener {
 	private final static Logger log = LoggerFactory.getLogger(ProxyArpManager.class);
 	
-	private final long ARP_ENTRY_TIMEOUT = 600000; //ms (== 10 mins)
-	
 	private final long ARP_TIMER_PERIOD = 60000; //ms (== 1 min) 
+	
+	private static final int ARP_REQUEST_TIMEOUT = 2000; //ms
 			
-	private IFloodlightProviderService floodlightProvider;
-	private ITopologyService topology;
-	private ILayer3InfoService layer3;
+	private final IFloodlightProviderService floodlightProvider;
+	private final ITopologyService topology;
+	private final ILayer3InfoService layer3;
+	private final IRestApiService restApi;
 	
-	private Map<InetAddress, ArpTableEntry> arpTable;
+	private final ArpCache arpCache;
 
-	private SetMultimap<InetAddress, ArpRequest> arpRequests;
+	private final SetMultimap<InetAddress, ArpRequest> arpRequests;
 	
-	private class ArpRequest {
+	private static class ArpRequest {
 		private final IArpRequester requester;
-		private boolean retry;
+		private final boolean retry;
 		private long requestTime;
 		
 		public ArpRequest(IArpRequester requester, boolean retry){
@@ -74,32 +74,52 @@
 		}
 		
 		public boolean isExpired() {
-			return (System.currentTimeMillis() - requestTime) 
-					> IProxyArpService.ARP_REQUEST_TIMEOUT;
+			return (System.currentTimeMillis() - requestTime) > ARP_REQUEST_TIMEOUT;
 		}
 		
 		public boolean shouldRetry() {
 			return retry;
 		}
 		
-		public void dispatchReply(InetAddress ipAddress, byte[] replyMacAddress) {
+		public void dispatchReply(InetAddress ipAddress, MACAddress replyMacAddress) {
 			requester.arpResponse(ipAddress, replyMacAddress);
 		}
 	}
 	
+	private class HostArpRequester implements IArpRequester {
+		private final ARP arpRequest;
+		private final long dpid;
+		private final short port;
+		
+		public HostArpRequester(ARP arpRequest, long dpid, short port) {
+			this.arpRequest = arpRequest;
+			this.dpid = dpid;
+			this.port = port;
+		}
+
+		@Override
+		public void arpResponse(InetAddress ipAddress, MACAddress macAddress) {
+			ProxyArpManager.this.sendArpReply(arpRequest, dpid, port, macAddress);
+		}
+	}
+	
 	public ProxyArpManager(IFloodlightProviderService floodlightProvider,
-				ITopologyService topology, ILayer3InfoService layer3){
+				ITopologyService topology, ILayer3InfoService layer3,
+				IRestApiService restApi){
 		this.floodlightProvider = floodlightProvider;
 		this.topology = topology;
 		this.layer3 = layer3;
+		this.restApi = restApi;
 		
-		arpTable = new HashMap<InetAddress, ArpTableEntry>();
+		arpCache = new ArpCache();
 
 		arpRequests = Multimaps.synchronizedSetMultimap(
 				HashMultimap.<InetAddress, ArpRequest>create());
 	}
 	
 	public void startUp() {
+		restApi.addRestletRoutable(new ArpWebRoutable());
+		
 		Timer arpTimer = new Timer("arp-processing");
 		arpTimer.scheduleAtFixedRate(new TimerTask() {
 			@Override
@@ -226,20 +246,20 @@
 						target.getHostAddress(), layer3.getRouterMacAddress());
 				
 				sendArpReply(arp, sw.getId(), pi.getInPort(), 
-						layer3.getRouterMacAddress().toBytes());
+						layer3.getRouterMacAddress());
 			}
 			
 			return;
 		}
 		
-		byte[] mac = lookupArpTable(arp.getTargetProtocolAddress());
+		MACAddress macAddress = arpCache.lookup(target);
 		
-		if (mac == null){
-			//Mac address is not in our arp table.
+		if (macAddress == null){
+			//MAC address is not in our ARP cache.
 			
 			//Record where the request came from so we know where to send the reply
 			arpRequests.put(target, new ArpRequest(
-					new HostArpRequester(this, arp, sw.getId(), pi.getInPort()), false));
+					new HostArpRequester(arp, sw.getId(), pi.getInPort()), false));
 						
 			//Flood the request out edge ports
 			sendArpRequestToSwitches(target, pi.getPacketData(), sw.getId(), pi.getInPort());
@@ -249,11 +269,11 @@
 			if (log.isTraceEnabled()) {
 				log.trace("Sending reply: {} => {} to host at {}/{}", new Object [] {
 						inetAddressToString(arp.getTargetProtocolAddress()),
-						MACAddress.valueOf(mac).toString(),
+						macAddress.toString(),
 						HexString.toHexString(sw.getId()), pi.getInPort()});
 			}
 			
-			sendArpReply(arp, sw.getId(), pi.getInPort(), mac);
+			sendArpReply(arp, sw.getId(), pi.getInPort(), macAddress);
 		}
 	}
 	
@@ -265,18 +285,20 @@
 					HexString.toHexString(sw.getId()), pi.getInPort()});
 		}
 		
-		updateArpTable(arp);
-		
-		//See if anyone's waiting for this ARP reply
-		InetAddress addr;
+		InetAddress senderIpAddress;
 		try {
-			addr = InetAddress.getByAddress(arp.getSenderProtocolAddress());
+			senderIpAddress = InetAddress.getByAddress(arp.getSenderProtocolAddress());
 		} catch (UnknownHostException e) {
 			log.debug("Invalid address in ARP reply", e);
 			return;
 		}
 		
-		Set<ArpRequest> requests = arpRequests.get(addr);
+		MACAddress senderMacAddress = MACAddress.valueOf(arp.getSenderHardwareAddress());
+		
+		arpCache.update(senderIpAddress, senderMacAddress);
+		
+		//See if anyone's waiting for this ARP reply
+		Set<ArpRequest> requests = arpRequests.get(senderIpAddress);
 		
 		//Synchronize on the Multimap while using an iterator for one of the sets
 		List<ArpRequest> requestsToSend = new ArrayList<ArpRequest>(requests.size());
@@ -291,57 +313,7 @@
 		
 		//Don't hold an ARP lock while dispatching requests
 		for (ArpRequest request : requestsToSend) {
-			request.dispatchReply(addr, arp.getSenderHardwareAddress());
-		}
-	}
-
-	private synchronized byte[] lookupArpTable(byte[] ipAddress){
-		InetAddress addr;
-		try {
-			addr = InetAddress.getByAddress(ipAddress);
-		} catch (UnknownHostException e) {
-			log.debug("Unable to create InetAddress", e);
-			return null;
-		}
-		
-		ArpTableEntry arpEntry = arpTable.get(addr);
-		
-		if (arpEntry == null){
-			return null;
-		}
-		
-		if (System.currentTimeMillis() - arpEntry.getTimeLastSeen() 
-				> ARP_ENTRY_TIMEOUT){
-			//Entry has timed out so we'll remove it and return null
-			log.trace("Removing expired ARP entry for {}", 
-					inetAddressToString(ipAddress));
-			
-			arpTable.remove(addr);
-			return null;
-		}
-		
-		return arpEntry.getMacAddress();
-	}
-	
-	private synchronized void updateArpTable(ARP arp){
-		InetAddress addr;
-		try {
-			addr = InetAddress.getByAddress(arp.getSenderProtocolAddress());
-		} catch (UnknownHostException e) {
-			log.debug("Unable to create InetAddress", e);
-			return;
-		}
-		
-		ArpTableEntry arpEntry = arpTable.get(addr);
-		
-		if (arpEntry != null 
-				&& arpEntry.getMacAddress() == arp.getSenderHardwareAddress()){
-			arpEntry.setTimeLastSeen(System.currentTimeMillis());
-		}
-		else {
-			arpTable.put(addr, 
-					new ArpTableEntry(arp.getSenderHardwareAddress(), 
-										System.currentTimeMillis()));
+			request.dispatchReply(senderIpAddress, senderMacAddress);
 		}
 	}
 	
@@ -501,24 +473,11 @@
 		}
 	}
 	
-	private String inetAddressToString(byte[] bytes) {
-		try {
-			return InetAddress.getByAddress(bytes).getHostAddress();
-		} catch (UnknownHostException e) {
-			log.debug("Invalid IP address", e);
-			return "";
-		}
-	}
-	
-	/*
-	 * IProxyArpService methods
-	 */
-	
-	public void sendArpReply(ARP arpRequest, long dpid, short port, byte[] targetMac) {
+	private void sendArpReply(ARP arpRequest, long dpid, short port, MACAddress targetMac) {
 		if (log.isTraceEnabled()) {
 			log.trace("Sending reply {} => {} to {}", new Object[] {
 					inetAddressToString(arpRequest.getTargetProtocolAddress()),
-					HexString.toHexString(targetMac),
+					targetMac,
 					inetAddressToString(arpRequest.getSenderProtocolAddress())});
 		}
 		
@@ -528,14 +487,14 @@
 			.setHardwareAddressLength((byte)Ethernet.DATALAYER_ADDRESS_LENGTH)
 			.setProtocolAddressLength((byte)IPv4.ADDRESS_LENGTH)
 			.setOpCode(ARP.OP_REPLY)
-			.setSenderHardwareAddress(targetMac)
+			.setSenderHardwareAddress(targetMac.toBytes())
 			.setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
 			.setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
 			.setTargetProtocolAddress(arpRequest.getSenderProtocolAddress());
 		
 		Ethernet eth = new Ethernet();
 		eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
-			.setSourceMACAddress(targetMac)
+			.setSourceMACAddress(targetMac.toBytes())
 			.setEtherType(Ethernet.TYPE_ARP)
 			.setPayload(arpReply);
 		
@@ -569,10 +528,23 @@
 			log.error("Failure writing packet out to switch", e);
 		}
 	}
+	
+	private String inetAddressToString(byte[] bytes) {
+		try {
+			return InetAddress.getByAddress(bytes).getHostAddress();
+		} catch (UnknownHostException e) {
+			log.debug("Invalid IP address", e);
+			return "";
+		}
+	}
+	
+	/*
+	 * IProxyArpService methods
+	 */
 
 	@Override
-	public byte[] getMacAddress(InetAddress ipAddress) {
-		return lookupArpTable(ipAddress.getAddress());
+	public MACAddress getMacAddress(InetAddress ipAddress) {
+		return arpCache.lookup(ipAddress);
 	}
 
 	@Override
@@ -585,4 +557,9 @@
 			sendArpRequestForAddress(ipAddress);
 		}
 	}
+	
+	@Override
+	public List<String> getMappings() {
+		return arpCache.getMappings();
+	}
 }
diff --git a/start-onos.sh b/start-onos.sh
index a35d181..14adfb0 100755
--- a/start-onos.sh
+++ b/start-onos.sh
@@ -71,7 +71,8 @@
   done
 
 # Create a logback file if required
-  cat <<EOF_LOGBACK >${ONOS_LOGBACK}
+  if [ ! -f ${ONOS_LOGBACK} ]; then
+    cat <<EOF_LOGBACK >${ONOS_LOGBACK}
 <configuration scan="true" debug="true">
 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
 <encoder>
@@ -95,6 +96,7 @@
 </root>
 </configuration>
 EOF_LOGBACK
+  fi
 
   # Run floodlight
   echo "Starting ONOS controller ..."