Renamed forwarding and proxyarp packages

net.onrc.onos.ofcontroller.forwarding => net.onrc.onos.apps.forwarding
net.onrc.onos.ofcontroller.proxyarp => net.onrc.onos.apps.proxyarp

Change-Id: Id368d4fd675b00ad84c17d44dd9804f010710cde
diff --git a/src/main/java/net/onrc/onos/apps/bgproute/BgpRoute.java b/src/main/java/net/onrc/onos/apps/bgproute/BgpRoute.java
index 062558e..32ce5e3 100644
--- a/src/main/java/net/onrc/onos/apps/bgproute/BgpRoute.java
+++ b/src/main/java/net/onrc/onos/apps/bgproute/BgpRoute.java
@@ -27,11 +27,11 @@
 import net.floodlightcontroller.restserver.IRestApiService;
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.apps.bgproute.RibUpdate.Operation;
+import net.onrc.onos.apps.proxyarp.IArpRequester;
+import net.onrc.onos.apps.proxyarp.IProxyArpService;
 import net.onrc.onos.ofcontroller.core.config.IConfigInfoService;
 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.util.CallerId;
 import net.onrc.onos.ofcontroller.util.DataPath;
 import net.onrc.onos.ofcontroller.util.Dpid;
diff --git a/src/main/java/net/onrc/onos/apps/forwarding/Forwarding.java b/src/main/java/net/onrc/onos/apps/forwarding/Forwarding.java
new file mode 100644
index 0000000..3d1623c
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/forwarding/Forwarding.java
@@ -0,0 +1,590 @@
+package net.onrc.onos.apps.forwarding;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.apps.proxyarp.BroadcastPacketOutNotification;
+import net.onrc.onos.apps.proxyarp.IProxyArpService;
+import net.onrc.onos.datagrid.IDatagridService;
+import net.onrc.onos.datagrid.IEventChannel;
+import net.onrc.onos.datagrid.IEventChannelListener;
+import net.onrc.onos.intent.Intent;
+import net.onrc.onos.intent.IntentMap;
+import net.onrc.onos.intent.IntentOperation;
+import net.onrc.onos.intent.IntentOperationList;
+import net.onrc.onos.intent.PathIntent;
+import net.onrc.onos.intent.ShortestPathIntent;
+import net.onrc.onos.intent.Intent.IntentState;
+import net.onrc.onos.intent.runtime.IPathCalcRuntimeService;
+import net.onrc.onos.intent.runtime.IntentStateList;
+import net.onrc.onos.ofcontroller.devicemanager.IOnosDeviceService;
+import net.onrc.onos.ofcontroller.flowprogrammer.IFlowPusherService;
+import net.onrc.onos.ofcontroller.networkgraph.Device;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
+import net.onrc.onos.ofcontroller.networkgraph.LinkEvent;
+import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+import net.onrc.onos.ofcontroller.networkgraph.Switch;
+import net.onrc.onos.ofcontroller.util.Dpid;
+import net.onrc.onos.ofcontroller.util.FlowPath;
+import net.onrc.onos.ofcontroller.util.Port;
+import net.onrc.onos.ofcontroller.util.SwitchPort;
+import net.onrc.onos.packet.Ethernet;
+import net.onrc.onos.registry.controller.IControllerRegistryService;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+
+public class Forwarding implements IOFMessageListener, IFloodlightModule,
+									IForwardingService, IEventChannelListener<Long, IntentStateList> {
+	private final static Logger log = LoggerFactory.getLogger(Forwarding.class);
+   
+	private final int SLEEP_TIME_FOR_DB_DEVICE_INSTALLED = 100; // milliseconds
+	private final static int NUMBER_OF_THREAD_FOR_EXECUTOR = 1;
+	
+	private final static ScheduledExecutorService executor = Executors.newScheduledThreadPool(NUMBER_OF_THREAD_FOR_EXECUTOR);
+	
+	private final String callerId = "Forwarding";
+	
+	private IFloodlightProviderService floodlightProvider;
+	private IFlowPusherService flowPusher;
+	private IDatagridService datagrid;
+
+	private IEventChannel<Long, BroadcastPacketOutNotification> eventChannel;
+	private static final String SINGLE_PACKET_OUT_CHANNEL_NAME = "onos.forwarding.packet_out";
+
+	private IControllerRegistryService controllerRegistryService;
+	
+	private INetworkGraphService networkGraphService;
+	private NetworkGraph networkGraph;
+	private IPathCalcRuntimeService pathRuntime;
+	private IntentMap intentMap;
+	
+	// TODO it seems there is a Guava collection that will time out entries.
+	// We should see if this will work here.
+	private Map<Path, PushedFlow> pendingFlows;
+	private ListMultimap<String, PacketToPush> waitingPackets;
+	
+	private final Object lock = new Object();
+	
+	private class PacketToPush {
+		public final OFPacketOut packet;
+		public final long dpid;
+		
+		public PacketToPush(OFPacketOut packet, long dpid) {
+			this.packet = packet;
+			this.dpid = dpid;
+		}
+	}
+	
+	private class PushedFlow {
+		public final String intentId;
+		public boolean installed = false;
+		public short firstOutPort;
+		
+		public PushedFlow(String flowId) {
+			this.intentId = flowId;
+		}
+	}
+	
+	private final class Path {
+		public final MACAddress srcMac;
+		public final MACAddress dstMac;
+		
+		public Path(MACAddress srcMac, MACAddress dstMac) {
+			this.srcMac = srcMac;
+			this.dstMac = dstMac;
+		}
+		
+		@Override
+		public boolean equals(Object other) {
+			if (!(other instanceof Path)) {
+				return false;
+			}
+			
+			Path otherPath = (Path) other;
+			return srcMac.equals(otherPath.srcMac) &&
+					dstMac.equals(otherPath.dstMac);
+		}
+		
+		@Override
+		public int hashCode() {
+			int hash = 17;
+			hash = 31 * hash + srcMac.hashCode();
+			hash = 31 * hash + dstMac.hashCode();
+			return hash;
+		}
+		
+		@Override
+		public String toString() {
+			return "(" + srcMac + ") => (" + dstMac + ")";
+		}
+	}
+	
+	@Override
+	public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+		List<Class<? extends IFloodlightService>> services = 
+				new ArrayList<Class<? extends IFloodlightService>>(1);
+		services.add(IForwardingService.class);
+		return services;
+	}
+
+	@Override
+	public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+		Map<Class<? extends IFloodlightService>, IFloodlightService> impls = 
+				new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(1);
+		impls.put(IForwardingService.class, this);
+		return impls;
+	}
+
+	@Override
+	public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+		List<Class<? extends IFloodlightService>> dependencies = 
+				new ArrayList<Class<? extends IFloodlightService>>();
+		dependencies.add(IFloodlightProviderService.class);
+		dependencies.add(IFlowPusherService.class);
+		dependencies.add(IControllerRegistryService.class);	
+		dependencies.add(IOnosDeviceService.class);
+		dependencies.add(IDatagridService.class);
+		dependencies.add(INetworkGraphService.class);
+		dependencies.add(IPathCalcRuntimeService.class);
+		// We don't use the IProxyArpService directly, but reactive forwarding
+		// requires it to be loaded and answering ARP requests
+		dependencies.add(IProxyArpService.class);
+		return dependencies;
+	}
+	
+	@Override
+	public void init(FloodlightModuleContext context) {
+		floodlightProvider = 
+				context.getServiceImpl(IFloodlightProviderService.class);
+		flowPusher = context.getServiceImpl(IFlowPusherService.class);
+		datagrid = context.getServiceImpl(IDatagridService.class);
+		controllerRegistryService = context.getServiceImpl(IControllerRegistryService.class);
+		networkGraphService = context.getServiceImpl(INetworkGraphService.class);
+        pathRuntime = context.getServiceImpl(IPathCalcRuntimeService.class);
+		
+		floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+
+		pendingFlows = new HashMap<Path, PushedFlow>();
+		waitingPackets = LinkedListMultimap.create();
+	}
+	
+	@Override
+	public void startUp(FloodlightModuleContext context) {
+
+		eventChannel = datagrid.createChannel(SINGLE_PACKET_OUT_CHANNEL_NAME,
+								Long.class,
+						      BroadcastPacketOutNotification.class);
+		networkGraph = networkGraphService.getNetworkGraph();
+		intentMap = pathRuntime.getPathIntents();
+		datagrid.addListener("onos.pathintent_state", this, Long.class, IntentStateList.class);
+	}
+
+	@Override
+	public String getName() {
+		return "onosforwarding";
+	}
+
+	@Override
+	public boolean isCallbackOrderingPrereq(OFType type, String name) {
+		return (type == OFType.PACKET_IN) && 
+				(name.equals("devicemanager") || name.equals("proxyarpmanager")
+				|| name.equals("onosdevicemanager"));
+	}
+
+	@Override
+	public boolean isCallbackOrderingPostreq(OFType type, String name) {
+		return false;
+	}
+
+	@Override
+	public Command receive(
+			IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+		
+		if (msg.getType() != OFType.PACKET_IN) {
+			return Command.CONTINUE;
+		}
+		
+		OFPacketIn pi = (OFPacketIn) msg;
+		
+		Ethernet eth = IFloodlightProviderService.bcStore.
+				get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+		
+		log.debug("Receive PACKET_IN swId {}, portId {}", sw.getId(), pi.getInPort());
+		
+		if (eth.getEtherType() != Ethernet.TYPE_IPv4) {
+			return Command.CONTINUE;
+		}
+		
+		if (eth.isBroadcast() || eth.isMulticast()) {
+			handleBroadcast(sw, pi, eth);
+		}
+		else {
+			// Unicast
+			handlePacketIn(sw, pi, eth);
+		}
+		
+		return Command.STOP;
+	}
+	
+	private void handleBroadcast(IOFSwitch sw, OFPacketIn pi, Ethernet eth) {
+		if (log.isTraceEnabled()) {
+			log.trace("Sending broadcast packet to other ONOS instances");
+		}
+
+		//We don't use address information, so 0 is put into the third argument.
+		BroadcastPacketOutNotification key =
+		    new BroadcastPacketOutNotification(
+						       eth.serialize(),
+						       0, sw.getId(),
+						       pi.getInPort());
+		eventChannel.addTransientEntry(eth.getDestinationMAC().toLong(), key);
+	}
+	
+	private void handlePacketIn(IOFSwitch sw, OFPacketIn pi, Ethernet eth){
+		log.debug("Start handlePacketIn swId {}, portId {}", sw.getId(), pi.getInPort());
+
+		String destinationMac = 
+				HexString.toHexString(eth.getDestinationMACAddress()); 
+		
+		//FIXME getDeviceByMac() is a blocking call, so it may be better way to handle it to avoid the condition.
+		Device deviceObject = networkGraph.getDeviceByMac(MACAddress.valueOf(destinationMac));
+
+		if (deviceObject == null) {
+			log.debug("No device entry found for {}",
+					destinationMac);
+
+			//Device is not in the DB, so wait it until the device is added.
+			executor.schedule(new WaitDeviceArp(sw, pi, eth), SLEEP_TIME_FOR_DB_DEVICE_INSTALLED, TimeUnit.MILLISECONDS);
+			return;
+		}
+
+		continueHandlePacketIn(sw, pi, eth, deviceObject);
+	}
+	
+	private class WaitDeviceArp implements Runnable {
+		IOFSwitch sw;
+		OFPacketIn pi;
+		Ethernet eth;
+
+		public WaitDeviceArp(IOFSwitch sw, OFPacketIn pi, Ethernet eth) {
+			super();
+			this.sw = sw;
+			this.pi = pi;
+			this.eth = eth;
+		}
+
+		@Override
+		public void run() {
+			Device deviceObject = networkGraph.getDeviceByMac(MACAddress.valueOf(eth.getDestinationMACAddress()));
+				if(deviceObject == null){
+					log.debug("wait {}ms and device was not found. Send broadcast packet and the thread finish.", SLEEP_TIME_FOR_DB_DEVICE_INSTALLED);
+					handleBroadcast(sw, pi, eth);
+					return;
+				}
+				log.debug("wait {}ms and device {} was found, continue",SLEEP_TIME_FOR_DB_DEVICE_INSTALLED, deviceObject.getMacAddress());
+				continueHandlePacketIn(sw, pi, eth, deviceObject);
+		}
+	}
+
+	private void continueHandlePacketIn(IOFSwitch sw, OFPacketIn pi, Ethernet eth, Device deviceObject) {
+
+		log.debug("Start continuehandlePacketIn");
+		
+		//Iterator<IPortObject> ports = deviceObject.getAttachedPorts().iterator();	
+		Iterator<net.onrc.onos.ofcontroller.networkgraph.Port> ports = deviceObject.getAttachmentPoints().iterator();	
+		if (!ports.hasNext()) {
+			log.debug("No attachment point found for device {} - broadcasting packet", 
+					deviceObject.getMacAddress());
+			handleBroadcast(sw, pi, eth);
+			return;	
+		}
+
+		//This code assumes the device has only one port. It should be problem.
+		net.onrc.onos.ofcontroller.networkgraph.Port portObject = ports.next();
+		short destinationPort = portObject.getNumber().shortValue();
+		Switch switchObject = portObject.getSwitch();
+		long destinationDpid = switchObject.getDpid();
+		
+		// TODO SwitchPort, Dpid and Port should probably be immutable
+		SwitchPort srcSwitchPort = new SwitchPort(
+				new Dpid(sw.getId()), new Port(pi.getInPort())); 
+		SwitchPort dstSwitchPort = new SwitchPort(
+				new Dpid(destinationDpid), new Port(destinationPort)); 
+				
+		MACAddress srcMacAddress = MACAddress.valueOf(eth.getSourceMACAddress());
+		MACAddress dstMacAddress = MACAddress.valueOf(eth.getDestinationMACAddress());
+		
+		synchronized (lock) {
+			//TODO check concurrency
+			Path pathspec = new Path(srcMacAddress, dstMacAddress);	
+			PushedFlow existingFlow = pendingFlows.get(pathspec);
+
+			//A path is installed side by side to reduce a path timeout and a wrong state.
+			if (existingFlow != null) {
+				// We've already start to install a flow for this pair of MAC addresses
+				if(log.isDebugEnabled()) {
+					log.debug("Found existing the same pathspec {}, intent ID is {}", 
+							pathspec, 
+							existingFlow.intentId);
+				}
+				
+				OFPacketOut po = constructPacketOut(pi, sw);
+				
+				// Find the correct port here. We just assume the PI is from 
+				// the first hop switch, but this is definitely not always
+				// the case. We'll have to retrieve the flow from HZ every time
+				// because it could change (be rerouted) sometimes.
+				if (existingFlow.installed) {
+					// Flow has been sent to the switches so it is safe to
+					// send a packet out now
+
+					Intent intent = intentMap.getIntent(existingFlow.intentId);
+					PathIntent pathIntent = null;
+					if(intent instanceof PathIntent) {
+						pathIntent = (PathIntent)intent;
+					} else {
+						log.debug("Intent {} is not PathIntent. Return.", intent.getId());
+						return;
+					}
+					
+					Boolean isflowEntryForThisSwitch = false;		
+					net.onrc.onos.ofcontroller.networkgraph.Path path = pathIntent.getPath();
+
+					for(Iterator<LinkEvent> i = path.iterator(); i.hasNext();) {
+						LinkEvent le = (LinkEvent)i.next();
+						if(le.getSrc().dpid == sw.getId()) {
+							log.debug("src {} dst {}", le.getSrc(), le.getDst());
+							isflowEntryForThisSwitch = true;
+							break;
+						}
+					}
+					
+					if (isflowEntryForThisSwitch == false) {
+						// If we don't find a flow entry for that switch, then we're
+						// in the middle of a rerouting (or something's gone wrong). 
+						// This packet will be dropped as a victim of the rerouting.
+						log.debug("Dropping packet on flow {} between {}-{}",
+								existingFlow.intentId,
+								srcMacAddress, dstMacAddress);
+					} else {
+						log.debug("Sending packet out from sw {}, outport{}", sw, existingFlow.firstOutPort);
+						sendPacketOut(sw, po, existingFlow.firstOutPort);
+					}
+				}
+				else {
+					// Flow path has not yet been installed to switches so save the
+					// packet out for later
+					log.debug("Put a packet into the waitng list. flowId {}", existingFlow.intentId);
+					waitingPackets.put(existingFlow.intentId, new PacketToPush(po, sw.getId()));
+				}
+				return;
+			}
+
+			log.debug("Adding new flow between {} at {} and {} at {}",
+					new Object[]{srcMacAddress, srcSwitchPort, dstMacAddress, dstSwitchPort});
+			
+			String intentId = callerId + ":" + controllerRegistryService.getNextUniqueId();
+	        IntentOperationList operations = new IntentOperationList();
+	        ShortestPathIntent intent = new ShortestPathIntent(intentId,
+	    			sw.getId(), pi.getInPort(), srcMacAddress.toLong(),
+	    			destinationDpid, destinationPort, dstMacAddress.toLong());
+	        IntentOperation.Operator operator = IntentOperation.Operator.ADD;
+	        operations.add(operator, intent);
+	        pathRuntime.executeIntentOperations(operations);
+
+			OFPacketOut po = constructPacketOut(pi, sw);
+			
+			// Add to waiting lists
+			pendingFlows.put(pathspec, new PushedFlow(intentId));
+			log.debug("Put a Path {} in the pending flow, intent ID {}", pathspec, intentId);
+			waitingPackets.put(intentId, new PacketToPush(po, sw.getId()));
+			log.debug("Put a Packet in the wating list. related pathspec {}", pathspec);
+			
+		}
+	}
+
+	private OFPacketOut constructPacketOut(OFPacketIn pi, IOFSwitch sw) {	
+		OFPacketOut po = new OFPacketOut();
+		po.setInPort(OFPort.OFPP_NONE)
+		.setInPort(pi.getInPort())
+		.setActions(new ArrayList<OFAction>())
+		.setLengthU(OFPacketOut.MINIMUM_LENGTH);
+		
+		if (sw.getBuffers() == 0) {
+			po.setBufferId(OFPacketOut.BUFFER_ID_NONE)
+			.setPacketData(pi.getPacketData())
+			.setLengthU(po.getLengthU() + po.getPacketData().length);
+		}
+		else {
+			po.setBufferId(pi.getBufferId());
+		}
+		
+		return po;
+	}
+
+	@Override
+	public void flowsInstalled(Collection<FlowPath> installedFlowPaths) {
+	}
+	
+	@Override
+	public void flowRemoved(FlowPath removedFlowPath) {
+	}
+	
+	public void flowRemoved(PathIntent removedIntent) {
+		if(log.isTraceEnabled()){
+			log.trace("Path {} was removed", removedIntent.getParentIntent().getId());
+		}
+
+		ShortestPathIntent spfIntent = (ShortestPathIntent) removedIntent.getParentIntent();
+		MACAddress srcMacAddress = MACAddress.valueOf(spfIntent.getSrcMac());
+		MACAddress dstMacAddress = MACAddress.valueOf(spfIntent.getDstMac());
+		Path removedPath = new Path(srcMacAddress, dstMacAddress);
+		
+		synchronized (lock) {
+			// There *shouldn't* be any packets queued if the flow has 
+			// just been removed. 
+			List<PacketToPush> packets = waitingPackets.removeAll(spfIntent.getId());
+			if (!packets.isEmpty()) {
+				log.warn("Removed flow {} has packets queued.",  spfIntent.getId());
+			}
+			pendingFlows.remove(removedPath);
+			log.debug("Removed from the pendingFlow: Path {}, Flow ID {}", removedPath, spfIntent.getId());
+		}
+	}
+	
+	private void flowInstalled(PathIntent installedPath) {	
+		if(log.isTraceEnabled()){
+			log.trace("Path {} was installed", installedPath.getParentIntent().getId());
+		}
+		
+		ShortestPathIntent spfIntent = (ShortestPathIntent) installedPath.getParentIntent();
+		MACAddress srcMacAddress = MACAddress.valueOf(spfIntent.getSrcMac());
+		MACAddress dstMacAddress = MACAddress.valueOf(spfIntent.getDstMac());
+		Path path = new Path(srcMacAddress, dstMacAddress);
+		log.debug("Path spec {}", path);
+		
+		// TODO waiting packets should time out. We could request a path that
+		// can't be installed right now because of a network partition. The path
+		// may eventually be installed, but we may have received thousands of 
+		// packets in the meantime and probably don't want to send very old packets.
+		
+		List<PacketToPush> packets = null;
+		net.onrc.onos.ofcontroller.networkgraph.Path graphPath = installedPath.getPath();
+		
+		log.debug("path{}", graphPath);
+		Short outPort = graphPath.get(0).getSrc().getNumber().shortValue();
+	
+		PushedFlow existingFlow = null;
+		
+		synchronized (lock) {
+			existingFlow = pendingFlows.get(path);
+
+			if (existingFlow != null) {
+			    existingFlow.installed = true;
+			    existingFlow.firstOutPort = outPort;
+			} else {
+				log.debug("ExistingFlow {} is null", path);
+				return;
+			}
+
+			//Check both existing flow are installed status.
+			if(existingFlow.installed){
+				packets = waitingPackets.removeAll(existingFlow.intentId);
+				if(log.isDebugEnabled()){
+					log.debug("removed my packets {} to push from waitingPackets. outPort {} size {}",
+							existingFlow.intentId, existingFlow.firstOutPort, packets.size());
+				}
+			}else{
+				log.debug("Forward or reverse flows hasn't been pushed yet. return");	
+				return;
+			}
+		}
+		
+		for (PacketToPush packet : packets) {
+			log.debug("Start packetToPush to sw {}, outPort {}, path {}", packet.dpid, existingFlow.firstOutPort, path);
+			IOFSwitch sw = floodlightProvider.getSwitches().get(packet.dpid);
+			sendPacketOut(sw, packet.packet, existingFlow.firstOutPort);
+		}
+	}
+	
+	private void sendPacketOut(IOFSwitch sw, OFPacketOut po, short outPort) {
+		po.getActions().add(new OFActionOutput(outPort));
+		po.setActionsLength((short)
+				(po.getActionsLength() + OFActionOutput.MINIMUM_LENGTH));
+		po.setLengthU(po.getLengthU() + OFActionOutput.MINIMUM_LENGTH);
+		
+		flowPusher.add(sw, po);
+	}
+
+	@Override
+	public void entryAdded(IntentStateList value) {
+		entryUpdated(value);
+		
+	}
+
+	@Override
+	public void entryRemoved(IntentStateList value) {
+		//no-op
+	}
+
+	@Override
+	public void entryUpdated(IntentStateList value) {
+		for (Entry<String, IntentState> entry: value.entrySet()) {
+			log.debug("path intent key {}, value {}", entry.getKey(), entry.getValue());
+			PathIntent pathIntent = (PathIntent) intentMap.getIntent(entry.getKey());
+			if (pathIntent == null)
+				continue;
+			
+			if (!(pathIntent.getParentIntent() instanceof ShortestPathIntent))
+				continue;
+
+			IntentState state = entry.getValue();
+			switch (state) {
+				case INST_REQ:
+					break;
+				case INST_ACK:
+					flowInstalled(pathIntent);
+					break;
+				case INST_NACK:
+					break;
+				case DEL_REQ:
+					break;
+				case DEL_ACK:
+					flowRemoved(pathIntent);
+					break;
+				case DEL_PENDING:
+					break;
+				default:
+					break;
+				}
+		}
+	}
+}
diff --git a/src/main/java/net/onrc/onos/apps/forwarding/IForwardingService.java b/src/main/java/net/onrc/onos/apps/forwarding/IForwardingService.java
new file mode 100644
index 0000000..78bf41c
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/forwarding/IForwardingService.java
@@ -0,0 +1,33 @@
+package net.onrc.onos.apps.forwarding;
+
+import java.util.Collection;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.onrc.onos.ofcontroller.util.FlowPath;
+
+/**
+ * Temporary interface that allows the Forwarding module to be
+ * notified when a flow has been installed.
+ * 
+ * This should be refactored to a listener framework in the future.
+ * @author jono
+ *
+ */
+public interface IForwardingService extends IFloodlightService {
+	/**
+	 * Notify the Forwarding module that a collection of flows has been
+	 * installed in the network.
+	 *
+	 * @param installedFlowPaths the collection of FlowPaths that have
+	 * been installed in the network.
+	 */
+	public void flowsInstalled(Collection<FlowPath> installedFlowPaths);
+	
+	/**
+	 * Notify the Forwarding module that a flow has expired and been 
+	 * removed from the network.
+	 * 
+	 * @param removedFlowPath The FlowPath that was removed
+	 */
+	public void flowRemoved(FlowPath removedFlowPath);
+}
diff --git a/src/main/java/net/onrc/onos/apps/proxyarp/ArpCache.java b/src/main/java/net/onrc/onos/apps/proxyarp/ArpCache.java
new file mode 100644
index 0000000..f2e2891
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/ArpCache.java
@@ -0,0 +1,143 @@
+package net.onrc.onos.apps.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;
+
+/*
+ * 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.
+ */
+
+/**
+ * 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 final long ARP_ENTRY_TIMEOUT = 60000; // ms (1 min)
+
+    // Protected by locking on the ArpCache object (this)
+    private final Map<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 > ARP_ENTRY_TIMEOUT;
+        }
+    }
+
+    /**
+     * Class constructor.
+     */
+    ArpCache() {
+        arpCache = new HashMap<InetAddress, ArpCacheEntry>();
+    }
+
+    /**
+     * 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());
+        } else {
+            arpCache.put(ipAddress, new ArpCacheEntry(macAddress));
+        }
+    }
+
+    /**
+     * Retrieve a list of all mappings in the ARP cache.
+     * @return list of all ARP mappings, formatted as a human-readable string
+     *
+     */
+    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/apps/proxyarp/ArpCacheResource.java b/src/main/java/net/onrc/onos/apps/proxyarp/ArpCacheResource.java
new file mode 100644
index 0000000..7bf2a5a
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/ArpCacheResource.java
@@ -0,0 +1,26 @@
+package net.onrc.onos.apps.proxyarp;
+
+import java.util.List;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * REST resource to view the IP to MAC mappings in the ARP cache.
+ *
+ */
+public class ArpCacheResource extends ServerResource {
+
+    /**
+     * Handler for a REST call to retrieve the ARP cache.
+     * @return list of mappings formatted as a human-readable string.
+     */
+    @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/apps/proxyarp/ArpReplyNotification.java b/src/main/java/net/onrc/onos/apps/proxyarp/ArpReplyNotification.java
new file mode 100644
index 0000000..bde2734
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/ArpReplyNotification.java
@@ -0,0 +1,46 @@
+package net.onrc.onos.apps.proxyarp;
+
+import java.io.Serializable;
+
+import net.floodlightcontroller.util.MACAddress;
+
+/**
+ * Inter-instance notification that an ARP reply has been received. The
+ * notification contains both the IP address and the MAC address.
+ */
+public class ArpReplyNotification implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private int targetAddress;
+    private MACAddress targetMacAddress;
+
+    protected ArpReplyNotification() {}
+    /**
+     * Class constructor.
+     * @param targetAddress IP address received from the ARP reply
+     * @param targetMacAddress MAC address received from the ARP reply
+     */
+    public ArpReplyNotification(int targetAddress,
+            MACAddress targetMacAddress) {
+        this.targetAddress = targetAddress;
+        this.targetMacAddress = targetMacAddress;
+    }
+
+    /**
+     * Returns the IP address of the ARP reply.
+     * @return the IP address
+     */
+    public int getTargetAddress() {
+        return targetAddress;
+    }
+
+    /**
+     * Returns the MAC address of the ARP reply.
+     * @return the MAC address
+     */
+    public MACAddress getTargetMacAddress() {
+        return targetMacAddress;
+    }
+
+}
diff --git a/src/main/java/net/onrc/onos/apps/proxyarp/ArpWebRoutable.java b/src/main/java/net/onrc/onos/apps/proxyarp/ArpWebRoutable.java
new file mode 100644
index 0000000..82847f2
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/ArpWebRoutable.java
@@ -0,0 +1,36 @@
+package net.onrc.onos.apps.proxyarp;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+/**
+ * Routing class for ARP module REST URLs.
+ */
+public class ArpWebRoutable implements RestletRoutable {
+
+    /**
+     * Get a router configured with ARP module REST URLs.
+     *
+     * @param context the restlet context to build a router with
+     * @return the router
+     */
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/cache/json", ArpCacheResource.class);
+        return router;
+    }
+
+    /**
+     * Get the base path of the ARP module URLs.
+     *
+     * @return the string base path
+     */
+    @Override
+    public String basePath() {
+        return "/wm/arp";
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/proxyarp/BroadcastPacketOutNotification.java b/src/main/java/net/onrc/onos/apps/proxyarp/BroadcastPacketOutNotification.java
new file mode 100644
index 0000000..c2097f2
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/BroadcastPacketOutNotification.java
@@ -0,0 +1,77 @@
+package net.onrc.onos.apps.proxyarp;
+
+
+
+// TODO This class is too generic to be handled by ProxyArpService.
+// TODO The generic broadcast packet shouldn't contain an IP address which is
+// only for ARP packets.
+/**
+ * Notification to all ONOS instances to broadcast this packet out the edge of
+ * the network. The edge is defined as any port that doesn't have a link to
+ * another switch. The one exception is the port that the packet was received
+ * on.
+ *
+ */
+public class BroadcastPacketOutNotification extends PacketOutNotification {
+
+    private static final long serialVersionUID = 1L;
+
+    private final int address;
+    private final long inSwitch;
+    private final short inPort;
+
+    protected BroadcastPacketOutNotification() {
+    	super();
+        this.address = -1;
+        this.inSwitch = -1;
+        this.inPort = -1;
+    }
+    /**
+     * Class constructor.
+     *
+     * @param packet
+     *        packet data to send in the packet-out
+     * @param address
+     *        target IP address if the packet is an ARP packet
+     * @param inSwitch
+     *        dpid of the switch the packet was received on
+     * @param inPort
+     *        port number of the receiving port
+     */
+    public BroadcastPacketOutNotification(byte[] packet, int address,
+            long inSwitch, short inPort) {
+        super(packet);
+
+        this.address = address;
+        this.inSwitch = inSwitch;
+        this.inPort = inPort;
+    }
+    
+    /**
+     * Get the dpid of the switch the packet was received on.
+     *
+     * @return receiving switch dpid
+     */
+    public long getInSwitch() {
+        return inSwitch;
+    }
+
+    /**
+     * Get the port number of the port the packet was received on.
+     *
+     * @return receiving port number
+     */
+    public short getInPort() {
+        return inPort;
+    }
+
+    /**
+     * Get the target IP address if the packet is an ARP packet.
+     *
+     * @return the target IP address for ARP packets, or null if the packet is
+     *         not an ARP packet
+     */
+    public int getTargetAddress() {
+        return address;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/proxyarp/IArpRequester.java b/src/main/java/net/onrc/onos/apps/proxyarp/IArpRequester.java
new file mode 100644
index 0000000..ddc0ab4
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/IArpRequester.java
@@ -0,0 +1,24 @@
+package net.onrc.onos.apps.proxyarp;
+
+import java.net.InetAddress;
+
+import net.floodlightcontroller.util.MACAddress;
+
+/**
+ * Callback interface for modules using the {@link IProxyArpService} to send ARP
+ * requests.
+ *
+ */
+public interface IArpRequester {
+    /**
+     * 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/apps/proxyarp/IProxyArpService.java b/src/main/java/net/onrc/onos/apps/proxyarp/IProxyArpService.java
new file mode 100644
index 0000000..1cdf18c
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/IProxyArpService.java
@@ -0,0 +1,42 @@
+package net.onrc.onos.apps.proxyarp;
+
+import java.net.InetAddress;
+import java.util.List;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.util.MACAddress;
+
+// Extends IFloodlightService so we can access it from REST API resources
+/**
+ * Provides ARP services to other modules.
+ */
+public interface IProxyArpService extends IFloodlightService {
+    /**
+     * Returns the MAC address if there is a valid entry in the cache. Otherwise
+     * returns null.
+     *
+     * @param ipAddress the IP address to request the ARP mapping for
+     * @return the MACAddress that maps to the specified IP address, or null if
+     *         no mapping is found
+     */
+    public MACAddress getMacAddress(InetAddress ipAddress);
+
+    /**
+     * Tell the IProxyArpService to send an ARP request for the IP address. The
+     * request will be broadcast out all edge ports in the network.
+     *
+     * @param ipAddress the IP address to send an ARP request for
+     * @param requester the {@link IArpRequester} object that will be called if
+     *                  a reply is received
+     * @param retry whether to keep sending requests until the MAC is learnt
+     */
+    public void sendArpRequest(InetAddress ipAddress, IArpRequester requester,
+            boolean retry);
+
+    /**
+     * Returns a snapshot of the entire ARP cache.
+     *
+     * @return a list of mappings formatted as a human-readable string
+     */
+    public List<String> getMappings();
+}
diff --git a/src/main/java/net/onrc/onos/apps/proxyarp/PacketOutNotification.java b/src/main/java/net/onrc/onos/apps/proxyarp/PacketOutNotification.java
new file mode 100644
index 0000000..bd64e59
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/PacketOutNotification.java
@@ -0,0 +1,28 @@
+package net.onrc.onos.apps.proxyarp;
+
+import java.io.Serializable;
+
+/**
+ * A PacketOutNotification contains data sent between ONOS instances that
+ * directs other instances to send a packet out a set of ports. This is an
+ * abstract base class that will be subclassed by specific types of
+ * notifications.
+ */
+public abstract class PacketOutNotification implements Serializable{
+
+    private static final long serialVersionUID = 1L;
+
+    protected final byte[] packet;
+    
+    /**
+     * Class constructor.
+     * @param packet the packet data to send in the packet-out
+     */
+    public PacketOutNotification() {
+    	packet = null;
+    }
+    
+    public PacketOutNotification(byte[] packet) {
+        this.packet = packet;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/proxyarp/ProxyArpManager.java b/src/main/java/net/onrc/onos/apps/proxyarp/ProxyArpManager.java
new file mode 100644
index 0000000..ef3bb83
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/ProxyArpManager.java
@@ -0,0 +1,936 @@
+package net.onrc.onos.apps.proxyarp;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.apps.bgproute.Interface;
+import net.onrc.onos.datagrid.IDatagridService;
+import net.onrc.onos.datagrid.IEventChannel;
+import net.onrc.onos.datagrid.IEventChannelListener;
+import net.onrc.onos.ofcontroller.core.config.IConfigInfoService;
+import net.onrc.onos.ofcontroller.devicemanager.IOnosDeviceService;
+import net.onrc.onos.ofcontroller.flowprogrammer.IFlowPusherService;
+import net.onrc.onos.ofcontroller.networkgraph.Device;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
+import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
+import net.onrc.onos.ofcontroller.networkgraph.Switch;
+import net.onrc.onos.ofcontroller.util.Dpid;
+import net.onrc.onos.ofcontroller.util.Port;
+import net.onrc.onos.ofcontroller.util.SwitchPort;
+import net.onrc.onos.packet.ARP;
+import net.onrc.onos.packet.Ethernet;
+import net.onrc.onos.packet.IPv4;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+
+public class ProxyArpManager implements IProxyArpService, IOFMessageListener,
+					IFloodlightModule {
+    private static final Logger log = LoggerFactory
+            .getLogger(ProxyArpManager.class);
+
+    private static final long ARP_TIMER_PERIOD = 100; // ms
+
+    private static final int ARP_REQUEST_TIMEOUT = 2000; // ms
+
+    private IFloodlightProviderService floodlightProvider;
+    private IDatagridService datagrid;
+    private IEventChannel<Long, ArpReplyNotification> arpReplyEventChannel;
+    private IEventChannel<Long, BroadcastPacketOutNotification> broadcastPacketOutEventChannel;
+    private IEventChannel<Long, SinglePacketOutNotification> singlePacketOutEventChannel;
+    private static final String ARP_REPLY_CHANNEL_NAME = "onos.arp_reply";
+    private static final String BROADCAST_PACKET_OUT_CHANNEL_NAME = "onos.broadcast_packet_out";
+    private static final String SINGLE_PACKET_OUT_CHANNEL_NAME = "onos.single_packet_out";
+    private ArpReplyEventHandler arpReplyEventHandler = new ArpReplyEventHandler();
+    private BroadcastPacketOutEventHandler broadcastPacketOutEventHandler = new BroadcastPacketOutEventHandler();  
+    private SinglePacketOutEventHandler singlePacketOutEventHandler = new SinglePacketOutEventHandler();
+
+    private IConfigInfoService configService;
+    private IRestApiService restApi;
+    private IFlowPusherService flowPusher;
+    
+	private INetworkGraphService networkGraphService;
+	private NetworkGraph networkGraph;
+	private IOnosDeviceService onosDeviceService;
+
+    private short vlan;
+    private static final short NO_VLAN = 0;
+
+    private SetMultimap<InetAddress, ArpRequest> arpRequests;
+
+    private class BroadcastPacketOutEventHandler implements
+    IEventChannelListener<Long, BroadcastPacketOutNotification> {
+
+		@Override
+		public void entryAdded(BroadcastPacketOutNotification value) {
+			if(log.isTraceEnabled()) {
+				log.trace("entryAdded ip{}, sw {}, port {}, packet {}", value.getTargetAddress(), value.getInSwitch(), value.getInPort(), value.packet.length);
+			}
+			BroadcastPacketOutNotification notification = (BroadcastPacketOutNotification) value;
+			broadcastArpRequestOutMyEdge(notification.packet,
+						     notification.getInSwitch(),
+						     notification.getInPort());
+		
+			// set timestamp
+			ByteBuffer buffer = ByteBuffer.allocate(4);
+			buffer.putInt(notification.getTargetAddress());
+			InetAddress addr = null;
+			try {
+				addr = InetAddress.getByAddress(buffer.array());
+			} catch (UnknownHostException e) {
+				log.error("Exception:", e);
+			}
+			
+			if (addr != null) {
+			    for (ArpRequest request : arpRequests.get(addr)) {
+			    	request.setRequestTime();
+			    }
+			}			
+		}
+		
+		@Override
+		public void entryUpdated(BroadcastPacketOutNotification value) {
+			log.debug("entryUpdated");
+		    // TODO: For now, entryUpdated() is processed as entryAdded()
+		    entryAdded(value);
+		}
+		
+		@Override
+		public void entryRemoved(BroadcastPacketOutNotification value) {
+			log.debug("entryRemoved");
+		    // TODO: Not implemented. Revisit when this module is refactored
+		}
+    }
+    
+    private class SinglePacketOutEventHandler implements
+		IEventChannelListener<Long, SinglePacketOutNotification> {
+		@Override
+		public void entryAdded(SinglePacketOutNotification packetOutNotification) {
+			log.debug("entryAdded");
+			SinglePacketOutNotification notification =
+			    (SinglePacketOutNotification) packetOutNotification;
+			sendArpRequestOutPort(notification.packet,
+					      notification.getOutSwitch(),
+					      notification.getOutPort());
+	
+			// set timestamp
+			ByteBuffer buffer = ByteBuffer.allocate(4);
+			buffer.putInt(notification.getTargetAddress());
+			InetAddress addr = null;
+			try {
+				addr = InetAddress.getByAddress(buffer.array());
+			} catch (UnknownHostException e) {
+				log.error("Exception:", e);
+			}
+			
+			if (addr != null) {
+			    for (ArpRequest request : arpRequests.get(addr)) {
+			    	request.setRequestTime();
+			    }
+			}		
+		}
+	
+		@Override
+		public void entryUpdated(SinglePacketOutNotification packetOutNotification) {
+			log.debug("entryUpdated");
+		    // TODO: For now, entryUpdated() is processed as entryAdded()
+		    entryAdded(packetOutNotification);
+		}
+	
+		@Override
+		public void entryRemoved(SinglePacketOutNotification packetOutNotification) {
+			log.debug("entryRemoved");
+		    // TODO: Not implemented. Revisit when this module is refactored
+		}
+    }
+
+    private class ArpReplyEventHandler implements
+	IEventChannelListener<Long, ArpReplyNotification> {
+    	
+	@Override
+	public void entryAdded(ArpReplyNotification arpReply) {
+	    log.debug("Received ARP reply notification for ip {}, mac {}",
+	    		arpReply.getTargetAddress(), arpReply.getTargetMacAddress());
+		ByteBuffer buffer = ByteBuffer.allocate(4);
+		buffer.putInt(arpReply.getTargetAddress());
+		InetAddress addr = null;
+		try {
+			addr = InetAddress.getByAddress(buffer.array());
+		} catch (UnknownHostException e) {
+			log.error("Exception:", e);
+		}
+	   
+		if(addr != null) {
+			sendArpReplyToWaitingRequesters(addr,
+				    arpReply.getTargetMacAddress());
+		}
+	}
+
+	@Override
+	public void entryUpdated(ArpReplyNotification arpReply) {
+	    // TODO: For now, entryUpdated() is processed as entryAdded()
+	    entryAdded(arpReply);
+	}
+
+	@Override
+	public void entryRemoved(ArpReplyNotification arpReply) {
+	    // TODO: Not implemented. Revisit when this module is refactored
+	}
+    }
+
+    private static class ArpRequest {
+        private final IArpRequester requester;
+        private final boolean retry;
+        private boolean sent = false;
+        private long requestTime;
+
+        public ArpRequest(IArpRequester requester, boolean retry) {
+            this.requester = requester;
+            this.retry = retry;
+        }
+
+        public ArpRequest(ArpRequest old) {
+            this.requester = old.requester;
+            this.retry = old.retry;
+        }
+
+        public boolean isExpired() {
+            return sent
+                    && ((System.currentTimeMillis() - requestTime) > ARP_REQUEST_TIMEOUT);
+        }
+
+        public boolean shouldRetry() {
+            return retry;
+        }
+
+        public void dispatchReply(InetAddress ipAddress,
+                MACAddress replyMacAddress) {
+            requester.arpResponse(ipAddress, replyMacAddress);
+        }
+
+        public void setRequestTime() {
+            this.requestTime = System.currentTimeMillis();
+            this.sent = true;
+        }
+    }
+
+    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 ARP getArpRequest() {
+			return arpRequest;
+		}
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l =
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IProxyArpService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+        Map<Class<? extends IFloodlightService>, IFloodlightService> m =
+                new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
+        m.put(IProxyArpService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> dependencies =
+                new ArrayList<Class<? extends IFloodlightService>>();
+        dependencies.add(IFloodlightProviderService.class);
+        dependencies.add(IRestApiService.class);
+        dependencies.add(IDatagridService.class);
+        dependencies.add(IConfigInfoService.class);
+        dependencies.add(IFlowPusherService.class);
+        dependencies.add(INetworkGraphService.class);
+        dependencies.add(IOnosDeviceService.class);
+        return dependencies;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context) {
+        this.floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); 
+        this.configService = context.getServiceImpl(IConfigInfoService.class);
+        this.restApi = context.getServiceImpl(IRestApiService.class);
+        this.datagrid = context.getServiceImpl(IDatagridService.class);
+        this.flowPusher = context.getServiceImpl(IFlowPusherService.class);
+        this.networkGraphService = context.getServiceImpl(INetworkGraphService.class);
+        this.onosDeviceService = context.getServiceImpl(IOnosDeviceService.class);
+
+        // arpCache = new ArpCache();
+
+        arpRequests = Multimaps.synchronizedSetMultimap(HashMultimap
+                .<InetAddress, ArpRequest>create());
+
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        this.vlan = configService.getVlan();
+        log.info("vlan set to {}", this.vlan);
+
+        restApi.addRestletRoutable(new ArpWebRoutable());
+        floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+		networkGraph = networkGraphService.getNetworkGraph();
+		
+	//
+	// Event notification setup: channels and event handlers
+	//	
+	broadcastPacketOutEventChannel = datagrid.addListener(BROADCAST_PACKET_OUT_CHANNEL_NAME,
+			     broadcastPacketOutEventHandler,
+			     Long.class,
+			     BroadcastPacketOutNotification.class);
+	
+	singlePacketOutEventChannel = datagrid.addListener(SINGLE_PACKET_OUT_CHANNEL_NAME,
+			     singlePacketOutEventHandler,
+			     Long.class,
+			     SinglePacketOutNotification.class);
+	
+	arpReplyEventChannel = datagrid.addListener(ARP_REPLY_CHANNEL_NAME,
+						    arpReplyEventHandler,
+						    Long.class,
+						    ArpReplyNotification.class);
+
+        Timer arpTimer = new Timer("arp-processing");
+        arpTimer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                doPeriodicArpProcessing();
+            }
+        }, 0, ARP_TIMER_PERIOD);
+    }
+
+    /*
+     * Function that runs periodically to manage the asynchronous request mechanism.
+     * It basically cleans up old ARP requests if we don't get a response for them.
+     * The caller can designate that a request should be retried indefinitely, and
+     * this task will handle that as well.
+     */
+    private void doPeriodicArpProcessing() {
+        SetMultimap<InetAddress, ArpRequest> retryList = HashMultimap
+                .<InetAddress, ArpRequest>create();
+
+        // Have to synchronize externally on the Multimap while using an
+        // iterator,
+        // even though it's a synchronizedMultimap
+        synchronized (arpRequests) {
+            Iterator<Map.Entry<InetAddress, ArpRequest>> it = arpRequests
+                    .entries().iterator();
+
+            while (it.hasNext()) {
+                Map.Entry<InetAddress, ArpRequest> entry = it.next();
+                ArpRequest request = entry.getValue();
+                if (request.isExpired()) {
+                    log.debug("Cleaning expired ARP request for {}", entry
+                            .getKey().getHostAddress());
+
+					// If the ARP request is expired and then delete the device
+					// TODO check whether this is OK from this thread
+					HostArpRequester requester = (HostArpRequester) request.requester;
+					ARP req = requester.getArpRequest();
+					Device targetDev = networkGraph.getDeviceByMac(MACAddress.valueOf(req.getTargetHardwareAddress()));
+					if(targetDev != null) {
+						onosDeviceService.deleteOnosDeviceByMac(MACAddress.valueOf(req.getTargetHardwareAddress()));
+						if (log.isDebugEnabled()) {
+							log.debug("RemoveDevice: {} due to no have not recieve the ARP reply", targetDev.getMacAddress());
+						}
+					}
+
+                    it.remove();
+
+                    if (request.shouldRetry()) {
+                        retryList.put(entry.getKey(), request);
+                    }
+                }
+            }
+        }
+
+        for (Map.Entry<InetAddress, Collection<ArpRequest>> entry : retryList
+                .asMap().entrySet()) {
+
+            InetAddress address = entry.getKey();
+
+            log.debug("Resending ARP request for {}", address.getHostAddress());
+
+            // Only ARP requests sent by the controller will have the retry flag
+            // set, so for now we can just send a new ARP request for that
+            // address.
+            sendArpRequestForAddress(address);
+
+            for (ArpRequest request : entry.getValue()) {
+                arpRequests.put(address, new ArpRequest(request));
+            }
+        }
+    }
+
+    @Override
+    public String getName() {
+        return "proxyarpmanager";
+    }
+
+    @Override
+    public boolean isCallbackOrderingPrereq(OFType type, String name) {
+        if (type == OFType.PACKET_IN) {
+            return "devicemanager".equals(name)
+                    || "onosdevicemanager".equals(name);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isCallbackOrderingPostreq(OFType type, String name) {
+        return type == OFType.PACKET_IN && "onosforwarding".equals(name);
+    }
+
+    @Override
+    public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+
+        OFPacketIn pi = (OFPacketIn) msg;
+
+        Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
+                IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+        if (eth.getEtherType() == Ethernet.TYPE_ARP) {
+            ARP arp = (ARP) eth.getPayload();
+            if (arp.getOpCode() == ARP.OP_REQUEST) {
+                handleArpRequest(sw, pi, arp, eth);
+            } else if (arp.getOpCode() == ARP.OP_REPLY) {
+                // For replies we simply send a notification via Hazelcast
+                sendArpReplyNotification(eth, pi);
+
+                // handleArpReply(sw, pi, arp);
+            }
+
+            // Stop ARP packets here
+            return Command.STOP;
+        }
+
+        // Propagate everything else
+        return Command.CONTINUE;
+    }
+
+    private void handleArpRequest(IOFSwitch sw, OFPacketIn pi, ARP arp,
+            Ethernet eth) {
+        if (log.isTraceEnabled()) {
+            log.trace("ARP request received for {}",
+                    inetAddressToString(arp.getTargetProtocolAddress()));
+        }
+
+        InetAddress target;
+        try {
+            target = InetAddress.getByAddress(arp.getTargetProtocolAddress());
+        } catch (UnknownHostException e) {
+            log.debug("Invalid address in ARP request", e);
+            return;
+        }
+
+        if (configService.fromExternalNetwork(sw.getId(), pi.getInPort())) {
+            // If the request came from outside our network, we only care if
+            // it was a request for one of our interfaces.
+            if (configService.isInterfaceAddress(target)) {
+                log.trace(
+                        "ARP request for our interface. Sending reply {} => {}",
+                        target.getHostAddress(),
+                        configService.getRouterMacAddress());
+
+                sendArpReply(arp, sw.getId(), pi.getInPort(),
+                        configService.getRouterMacAddress());
+            }
+
+            return;
+        }
+
+        // MACAddress macAddress = arpCache.lookup(target);
+
+		arpRequests.put(target, new ArpRequest(
+				new HostArpRequester(arp, sw.getId(), pi.getInPort()), false));
+		
+		Device targetDevice = networkGraph.getDeviceByMac(MACAddress.valueOf(arp.getTargetHardwareAddress()));
+
+		if (targetDevice == null) {
+			if (log.isTraceEnabled()) {
+				log.trace("No device info found for {} - broadcasting",
+						target.getHostAddress());
+			}
+			
+			// We don't know the device so broadcast the request out
+			BroadcastPacketOutNotification key =
+					new BroadcastPacketOutNotification(eth.serialize(),
+							ByteBuffer.wrap(arp.getTargetProtocolAddress()).getInt(), sw.getId(), pi.getInPort());
+			log.debug("broadcastPacketOutEventChannel mac {}, ip {}, dpid {}, port {}, paket {}", eth.getSourceMAC().toLong(), 
+					ByteBuffer.wrap(arp.getTargetProtocolAddress()).getInt(), sw.getId(), pi.getInPort(), eth.serialize().length);
+			broadcastPacketOutEventChannel.addTransientEntry(eth.getDestinationMAC().toLong(), key);
+		}
+		else {
+			// Even if the device exists in our database, we do not reply to
+			// the request directly, but check whether the device is still valid
+			MACAddress macAddress = MACAddress.valueOf(arp.getTargetHardwareAddress());
+
+			if (log.isTraceEnabled()) {
+				log.trace("The target Device Record in DB is: {} => {} from ARP request host at {}/{}",
+						new Object [] {
+						inetAddressToString(arp.getTargetProtocolAddress()),
+						macAddress,
+						HexString.toHexString(sw.getId()), pi.getInPort()});
+			}
+
+			// sendArpReply(arp, sw.getId(), pi.getInPort(), macAddress);
+
+			Iterable<net.onrc.onos.ofcontroller.networkgraph.Port> outPorts = targetDevice.getAttachmentPoints();
+
+			if (!outPorts.iterator().hasNext()){
+				if (log.isTraceEnabled()) {
+					log.trace("Device {} exists but is not connected to any ports" + 
+							" - broadcasting", macAddress);
+				}
+				
+//				BroadcastPacketOutNotification key =
+//						new BroadcastPacketOutNotification(eth.serialize(), 
+//								target, sw.getId(), pi.getInPort());
+//				broadcastPacketOutEventChannel.addTransientEntry(eth.getDestinationMAC().toLong(), key);
+			} 
+			else {
+				for (net.onrc.onos.ofcontroller.networkgraph.Port portObject : outPorts) {
+					//long outSwitch = 0;
+					//short outPort = 0;
+
+					if(portObject.getOutgoingLink() != null || portObject.getIncomingLink() != null) {
+						continue;
+					}
+					
+					short outPort = portObject.getNumber().shortValue();
+					Switch outSwitchObject = portObject.getSwitch();
+					long outSwitch = outSwitchObject.getDpid();
+					
+					if (log.isTraceEnabled()) {
+						log.trace("Probing device {} on port {}/{}", 
+								new Object[] {macAddress, 
+								HexString.toHexString(outSwitch), outPort});
+					}
+					
+					SinglePacketOutNotification key =
+						    new SinglePacketOutNotification(eth.serialize(), 
+						    		ByteBuffer.wrap(target.getAddress()).getInt(), outSwitch, outPort);
+					singlePacketOutEventChannel.addTransientEntry(eth.getDestinationMAC().toLong(), key);
+				}
+			}
+		}
+    }
+
+    // Not used because device manager currently updates the database
+    // for ARP replies. May be useful in the future.
+    private void handleArpReply(IOFSwitch sw, OFPacketIn pi, ARP arp) {
+        if (log.isTraceEnabled()) {
+            log.trace("ARP reply recieved: {} => {}, on {}/{}", new Object[] {
+                    inetAddressToString(arp.getSenderProtocolAddress()),
+                    HexString.toHexString(arp.getSenderHardwareAddress()),
+                    HexString.toHexString(sw.getId()), pi.getInPort()});
+        }
+
+        InetAddress senderIpAddress;
+        try {
+            senderIpAddress = InetAddress.getByAddress(arp
+                    .getSenderProtocolAddress());
+        } catch (UnknownHostException e) {
+            log.debug("Invalid address in ARP reply", e);
+            return;
+        }
+
+        MACAddress senderMacAddress = MACAddress.valueOf(arp
+                .getSenderHardwareAddress());
+
+        // 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());
+        synchronized (arpRequests) {
+            Iterator<ArpRequest> it = requests.iterator();
+            while (it.hasNext()) {
+                ArpRequest request = it.next();
+                it.remove();
+                requestsToSend.add(request);
+            }
+        }
+
+        // Don't hold an ARP lock while dispatching requests
+        for (ArpRequest request : requestsToSend) {
+            request.dispatchReply(senderIpAddress, senderMacAddress);
+        }
+    }
+
+    private void sendArpRequestForAddress(InetAddress ipAddress) {
+        // TODO what should the sender IP address and MAC address be if no
+        // IP addresses are configured? Will there ever be a need to send
+        // ARP requests from the controller in that case?
+        // All-zero MAC address doesn't seem to work - hosts don't respond to it
+
+        byte[] zeroIpv4 = {0x0, 0x0, 0x0, 0x0};
+        byte[] zeroMac = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+        byte[] genericNonZeroMac = {0x0, 0x0, 0x0, 0x0, 0x0, 0x01};
+        byte[] broadcastMac = {(byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff};
+
+        ARP arpRequest = new ARP();
+
+        arpRequest
+                .setHardwareType(ARP.HW_TYPE_ETHERNET)
+                .setProtocolType(ARP.PROTO_TYPE_IP)
+                .setHardwareAddressLength(
+                        (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+                .setProtocolAddressLength((byte) IPv4.ADDRESS_LENGTH)
+                .setOpCode(ARP.OP_REQUEST).setTargetHardwareAddress(zeroMac)
+                .setTargetProtocolAddress(ipAddress.getAddress());
+
+        MACAddress routerMacAddress = configService.getRouterMacAddress();
+        // TODO hack for now as it's unclear what the MAC address should be
+        byte[] senderMacAddress = genericNonZeroMac;
+        if (routerMacAddress != null) {
+            senderMacAddress = routerMacAddress.toBytes();
+        }
+        arpRequest.setSenderHardwareAddress(senderMacAddress);
+
+        byte[] senderIPAddress = zeroIpv4;
+        Interface intf = configService.getOutgoingInterface(ipAddress);
+        if (intf != null) {
+            senderIPAddress = intf.getIpAddress().getAddress();
+        }
+
+        arpRequest.setSenderProtocolAddress(senderIPAddress);
+
+        Ethernet eth = new Ethernet();
+        eth.setSourceMACAddress(senderMacAddress)
+                .setDestinationMACAddress(broadcastMac)
+                .setEtherType(Ethernet.TYPE_ARP).setPayload(arpRequest);
+
+        if (vlan != NO_VLAN) {
+            eth.setVlanID(vlan).setPriorityCode((byte) 0);
+        }
+
+        // sendArpRequestToSwitches(ipAddress, eth.serialize());
+		SinglePacketOutNotification key =
+		    new SinglePacketOutNotification(eth.serialize(), ByteBuffer.wrap(ipAddress.getAddress()).getInt(),
+						    intf.getDpid(), intf.getPort());
+		singlePacketOutEventChannel.addTransientEntry(MACAddress.valueOf(senderMacAddress).toLong(), key);
+    }
+    
+    private void sendArpRequestToSwitches(InetAddress dstAddress, byte[] arpRequest) {
+    		sendArpRequestToSwitches(dstAddress, arpRequest, 0,
+    		OFPort.OFPP_NONE.getValue());
+    }
+
+    private void sendArpRequestToSwitches(InetAddress dstAddress,
+            byte[] arpRequest, long inSwitch, short inPort) {
+
+        if (configService.hasLayer3Configuration()) {
+            Interface intf = configService.getOutgoingInterface(dstAddress);
+            if (intf == null) {
+                // TODO here it should be broadcast out all non-interface edge
+                // ports.
+                // I think we can assume that if it's not a request for an
+                // external
+                // network, it's an ARP for a host in our own network. So we
+                // want to
+                // send it out all edge ports that don't have an interface
+                // configured
+                // to ensure it reaches all hosts in our network.
+                log.debug("No interface found to send ARP request for {}",
+                        dstAddress.getHostAddress());
+            } else {
+                sendArpRequestOutPort(arpRequest, intf.getDpid(),
+                        intf.getPort());
+            }
+        } else {
+            // broadcastArpRequestOutEdge(arpRequest, inSwitch, inPort);
+            broadcastArpRequestOutMyEdge(arpRequest, inSwitch, inPort);
+        }
+    }
+
+    private void sendArpReplyNotification(Ethernet eth, OFPacketIn pi) {
+        ARP arp = (ARP) eth.getPayload();
+
+        if (log.isTraceEnabled()) {
+            log.trace("Sending ARP reply for {} to other ONOS instances",
+                    inetAddressToString(arp.getSenderProtocolAddress()));
+        }
+
+        InetAddress targetAddress;
+
+        try {
+            targetAddress = InetAddress.getByAddress(arp
+                    .getSenderProtocolAddress());
+        } catch (UnknownHostException e) {
+            log.error("Unknown host", e);
+            return;
+        }
+
+        MACAddress mac = new MACAddress(arp.getSenderHardwareAddress());
+
+		ArpReplyNotification key =
+		    new ArpReplyNotification(ByteBuffer.wrap(targetAddress.getAddress()).getInt(), mac);
+		log.debug("ArpReplyNotification ip {}, mac{}", ByteBuffer.wrap(targetAddress.getAddress()).getInt(), mac);
+		arpReplyEventChannel.addTransientEntry(mac.toLong(), key);
+    }
+
+    private void broadcastArpRequestOutMyEdge(byte[] arpRequest, long inSwitch,
+            short inPort) {
+        List<SwitchPort> switchPorts = new ArrayList<SwitchPort>();
+
+        for (IOFSwitch sw : floodlightProvider.getSwitches().values()) {
+
+            OFPacketOut po = new OFPacketOut();
+            po.setInPort(OFPort.OFPP_NONE).setBufferId(-1)
+                    .setPacketData(arpRequest);
+
+            List<OFAction> actions = new ArrayList<OFAction>();
+
+			Switch graphSw = networkGraph.getSwitch(sw.getId());
+			Collection<net.onrc.onos.ofcontroller.networkgraph.Port> ports = graphSw.getPorts();
+			
+			if (ports == null) {
+				continue;
+			}
+			
+			for (net.onrc.onos.ofcontroller.networkgraph.Port portObject : ports) {
+				if (portObject.getOutgoingLink() == null && portObject.getNumber() > 0) {
+					Long portNumber = portObject.getNumber();
+					
+					if (sw.getId() == inSwitch && portNumber.shortValue() == inPort) {
+						// This is the port that the ARP message came in,
+						// so don't broadcast out this port
+						continue;
+					}		
+					switchPorts.add(new SwitchPort(new Dpid(sw.getId()), 
+							new net.onrc.onos.ofcontroller.util.Port(portNumber.shortValue())));
+					actions.add(new OFActionOutput(portNumber.shortValue()));
+				}
+			}
+
+            po.setActions(actions);
+            short actionsLength = (short) (actions.size() * OFActionOutput.MINIMUM_LENGTH);
+            po.setActionsLength(actionsLength);
+            po.setLengthU(OFPacketOut.MINIMUM_LENGTH + actionsLength
+                    + arpRequest.length);
+
+            flowPusher.add(sw, po);
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace("Broadcast ARP request to: {}", switchPorts);
+        }
+    }
+
+    private void sendArpRequestOutPort(byte[] arpRequest, long dpid, short port) {
+        if (log.isTraceEnabled()) {
+            log.trace("Sending ARP request out {}/{}",
+                    HexString.toHexString(dpid), port);
+        }
+
+        OFPacketOut po = new OFPacketOut();
+        po.setInPort(OFPort.OFPP_NONE).setBufferId(-1)
+                .setPacketData(arpRequest);
+
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(new OFActionOutput(port));
+        po.setActions(actions);
+        short actionsLength = (short) (actions.size() * OFActionOutput.MINIMUM_LENGTH);
+        po.setActionsLength(actionsLength);
+        po.setLengthU(OFPacketOut.MINIMUM_LENGTH + actionsLength
+                + arpRequest.length);
+
+        IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+
+        if (sw == null) {
+            log.warn("Switch not found when sending ARP request");
+            return;
+        }
+
+        flowPusher.add(sw, po);
+    }
+
+    private void sendArpReply(ARP arpRequest, long dpid, short port,
+            MACAddress targetMac) {
+        if (log.isTraceEnabled()) {
+            log.trace(
+                    "Sending reply {} => {} to {}",
+                    new Object[] {
+                            inetAddressToString(arpRequest
+                                    .getTargetProtocolAddress()),
+                            targetMac,
+                            inetAddressToString(arpRequest
+                                    .getSenderProtocolAddress())});
+        }
+
+        ARP arpReply = new ARP();
+        arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
+                .setProtocolType(ARP.PROTO_TYPE_IP)
+                .setHardwareAddressLength(
+                        (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+                .setProtocolAddressLength((byte) IPv4.ADDRESS_LENGTH)
+                .setOpCode(ARP.OP_REPLY)
+                .setSenderHardwareAddress(targetMac.toBytes())
+                .setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
+                .setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
+                .setTargetProtocolAddress(arpRequest.getSenderProtocolAddress());
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
+                .setSourceMACAddress(targetMac.toBytes())
+                .setEtherType(Ethernet.TYPE_ARP).setPayload(arpReply);
+
+        if (vlan != NO_VLAN) {
+            eth.setVlanID(vlan).setPriorityCode((byte) 0);
+        }
+
+        List<OFAction> actions = new ArrayList<OFAction>();
+        actions.add(new OFActionOutput(port));
+
+        OFPacketOut po = new OFPacketOut();
+        po.setInPort(OFPort.OFPP_NONE)
+                .setBufferId(-1)
+                .setPacketData(eth.serialize())
+                .setActions(actions)
+                .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+                .setLengthU(
+                        OFPacketOut.MINIMUM_LENGTH
+                                + OFActionOutput.MINIMUM_LENGTH
+                                + po.getPacketData().length);
+
+        List<OFMessage> msgList = new ArrayList<OFMessage>();
+        msgList.add(po);
+
+        IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+
+        if (sw == null) {
+            log.warn("Switch {} not found when sending ARP reply",
+                    HexString.toHexString(dpid));
+            return;
+        }
+
+        flowPusher.add(sw, po);
+    }
+
+    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 MACAddress getMacAddress(InetAddress ipAddress) {
+        // return arpCache.lookup(ipAddress);
+        return null;
+    }
+
+    @Override
+    public void sendArpRequest(InetAddress ipAddress, IArpRequester requester,
+            boolean retry) {
+        arpRequests.put(ipAddress, new ArpRequest(requester, retry));
+
+        // Sanity check to make sure we don't send a request for our own address
+        if (!configService.isInterfaceAddress(ipAddress)) {
+            sendArpRequestForAddress(ipAddress);
+        }
+    }
+
+    @Override
+    public List<String> getMappings() {
+        return new ArrayList<String>();
+    }
+
+    private void sendArpReplyToWaitingRequesters(InetAddress address,
+            MACAddress mac) {
+        log.debug("Sending ARP reply for {} to requesters",
+                address.getHostAddress());
+
+        // See if anyone's waiting for this ARP reply
+        Set<ArpRequest> requests = arpRequests.get(address);
+
+        // Synchronize on the Multimap while using an iterator for one of the
+        // sets
+        List<ArpRequest> requestsToSend = new ArrayList<ArpRequest>(
+                requests.size());
+        synchronized (arpRequests) {
+            Iterator<ArpRequest> it = requests.iterator();
+            while (it.hasNext()) {
+                ArpRequest request = it.next();
+                it.remove();
+                requestsToSend.add(request);
+            }
+        }
+
+        //TODO here, comment outed from long time ago. I will check if we need it later.
+        /*IDeviceObject deviceObject = deviceStorage.getDeviceByIP(
+        		InetAddresses.coerceToInteger(address));
+
+        MACAddress mac = MACAddress.valueOf(deviceObject.getMACAddress());
+
+        log.debug("Found {} at {} in network map",
+        		address.getHostAddress(), mac);*/
+
+        // Don't hold an ARP lock while dispatching requests
+        for (ArpRequest request : requestsToSend) {
+            request.dispatchReply(address, mac);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/apps/proxyarp/SinglePacketOutNotification.java b/src/main/java/net/onrc/onos/apps/proxyarp/SinglePacketOutNotification.java
new file mode 100644
index 0000000..8ee255b
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/proxyarp/SinglePacketOutNotification.java
@@ -0,0 +1,57 @@
+package net.onrc.onos.apps.proxyarp;
+
+
+
+// TODO This class is too generic to be handled by ProxyArpService.
+/**
+ * Notification to another ONOS instance to send a packet out a single port.
+ */
+public class SinglePacketOutNotification extends PacketOutNotification {
+
+    private static final long serialVersionUID = 1L;
+
+    private final int address;
+    private final long outSwitch;
+    private final short outPort;
+
+    /**
+     * Class constructor.
+     * @param packet the packet data to send in the packet-out
+     * @param address target IP address if the packet is an ARP packet
+     * @param outSwitch the dpid of the switch to send the packet on
+     * @param outPort the port number of the port to send the packet out
+     */
+    public SinglePacketOutNotification(byte[] packet, int address,
+            long outSwitch, short outPort) {
+        super(packet);
+
+        this.address = address;
+        this.outSwitch = outSwitch;
+        this.outPort = outPort;
+    }
+
+    /**
+     * Get the dpid of the switch the packet will be sent out.
+     * @return the switch's dpid
+     */
+    public long getOutSwitch() {
+        return outSwitch;
+    }
+
+    /**
+     * Get the port number of the port the packet will be sent out.
+     * @return the port number
+     */
+    public short getOutPort() {
+        return outPort;
+    }
+
+    /**
+     * Get the target IP address if the packet is an ARP packet.
+     * @return the target IP address for ARP packets, or null if the packet is
+     *         not an ARP packet
+     */
+    public int getTargetAddress() {
+        return address;
+    }
+}