Merge branch 'proxyarp'
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpPeer.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpPeer.java
new file mode 100644
index 0000000..7425a07
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpPeer.java
@@ -0,0 +1,42 @@
+package net.onrc.onos.ofcontroller.bgproute;
+
+import java.net.InetAddress;
+
+import net.floodlightcontroller.util.MACAddress;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import com.google.common.net.InetAddresses;
+
+public class BgpPeer {
+	private String interfaceName;
+	private InetAddress ipAddress;
+	private MACAddress macAddress;
+	
+	public String getInterfaceName() {
+		return interfaceName;
+	}
+	
+	@JsonProperty("interface")
+	public void setInterfaceName(String interfaceName) {
+		this.interfaceName = interfaceName;
+	}
+	
+	public InetAddress getIpAddress() {
+		return ipAddress;
+	}
+	
+	@JsonProperty("ipAddress")
+	public void setIpAddress(String ipAddress) {
+		this.ipAddress = InetAddresses.forString(ipAddress);
+	}
+	
+	public MACAddress getMacAddress() {
+		return macAddress;
+	}
+	
+	@JsonProperty("macAddress")
+	public void setMacAddress(String macAddress) {
+		this.macAddress = MACAddress.valueOf(macAddress);
+	}
+}
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 4b6e328..c57d4d8 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
@@ -6,11 +6,15 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 import net.floodlightcontroller.core.IFloodlightProviderService;
 import net.floodlightcontroller.core.IOFSwitch;
@@ -19,15 +23,22 @@
 import net.floodlightcontroller.core.module.FloodlightModuleException;
 import net.floodlightcontroller.core.module.IFloodlightModule;
 import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.SingletonTask;
 import net.floodlightcontroller.devicemanager.IDeviceService;
 import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
 import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.routing.Link;
 import net.floodlightcontroller.topology.ITopologyListener;
 import net.floodlightcontroller.topology.ITopologyService;
+import net.onrc.onos.ofcontroller.core.INetMapTopologyService.ITopoLinkService;
 import net.onrc.onos.ofcontroller.core.INetMapTopologyService.ITopoRouteService;
+import net.onrc.onos.ofcontroller.core.internal.TopoLinkServiceImpl;
 import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery;
 import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+import net.onrc.onos.ofcontroller.proxyarp.ProxyArpManager;
 import net.onrc.onos.ofcontroller.util.DataPath;
+import net.onrc.onos.ofcontroller.util.Dpid;
 import net.onrc.onos.ofcontroller.util.FlowEntry;
 import net.onrc.onos.ofcontroller.util.Port;
 import net.onrc.onos.ofcontroller.util.SwitchPort;
@@ -63,12 +74,12 @@
 	protected IDeviceService devices;
 	protected IRestApiService restApi;
 	
+	protected ProxyArpManager proxyArp;
+	
 	protected static Ptree ptree;
 	protected String bgpdRestIp;
 	protected String routerId;
-	protected String gatewaysFilename = "gateways.json";
-	
-	protected Set<InetAddress> routerIpAddresses;
+	protected String gatewaysFilename = "config.json";
 	
 	//We need to identify our flows somehow. But like it says in LearningSwitch.java,
 	//the controller/OS should hand out cookie IDs to prevent conflicts.
@@ -77,17 +88,73 @@
 	protected final long L2_FWD_COOKIE = APP_COOKIE + 1;
 	//Cookie for flows in ingress switches that rewrite the MAC address
 	protected final long MAC_RW_COOKIE = APP_COOKIE + 2;
+	//Cookie for flows that setup BGP paths
+	protected final long BGP_COOKIE = APP_COOKIE + 3;
 	//Forwarding uses priority 0, and the mac rewrite entries in ingress switches
 	//need to be higher priority than this otherwise the rewrite may not get done
 	protected final short SDNIP_PRIORITY = 10;
 	
+	protected final short BGP_PORT = 179;
+	
+	protected final int TOPO_DETECTION_WAIT = 2; //seconds
+	
+	//Configuration stuff
 	protected Map<String, GatewayRouter> gatewayRouters;
 	protected List<String> switches;
+	protected Map<String, Interface> interfaces;
+	protected List<BgpPeer> bgpPeers;
+	protected SwitchPort bgpdAttachmentPoint;
 	
 	//True when all switches have connected
 	protected volatile boolean switchesConnected = false;
 	//True when we have a full mesh of shortest paths between gateways
 	protected volatile boolean topologyReady = false;
+
+	//protected ConcurrentSkipListSet<LDUpdate> linkUpdates;
+	protected ArrayList<LDUpdate> linkUpdates;
+	protected SingletonTask topologyChangeDetectorTask;
+	
+	//protected ILinkStorage linkStorage;//XXX
+	
+	protected class TopologyChangeDetector implements Runnable {
+		@Override
+		public void run() {
+			log.debug("Running topology change detection task");
+			synchronized (linkUpdates) {
+				//This is the model the REST API uses to retrive network graph info
+				ITopoLinkService topoLinkService = new TopoLinkServiceImpl();
+				
+				List<Link> activeLinks = topoLinkService.getActiveLinks();
+				for (Link l : activeLinks){
+					log.debug("active link: {}", l);
+				}
+				
+				Iterator<LDUpdate> it = linkUpdates.iterator();
+				while (it.hasNext()){
+					LDUpdate ldu = it.next();
+					Link l = new Link(ldu.getSrc(), ldu.getSrcPort(), 
+							ldu.getDst(), ldu.getDstPort());
+					
+					if (activeLinks.contains(l)){
+						log.debug("Not found: {}", l);
+						it.remove();
+					}
+				}
+			}
+			
+			if (linkUpdates.isEmpty()){
+				//All updates have been seen in network map.
+				//We can check if topology is ready
+				log.debug("No know changes outstanding. Checking topology now");
+				checkStatus();
+			}
+			else {
+				//We know of some link updates that haven't propagated to the database yet
+				log.debug("Some changes not found in network map- size {}", linkUpdates.size());
+				topologyChangeDetectorTask.reschedule(TOPO_DETECTION_WAIT, TimeUnit.SECONDS);
+			}
+		}
+	}
 	
 	private void readGatewaysConfiguration(String gatewaysFilename){
 		File gatewaysFile = new File(gatewaysFilename);
@@ -98,10 +165,14 @@
 			
 			gatewayRouters = config.getGateways();
 			switches = config.getSwitches();
+			interfaces = config.getInterfaces();
+			bgpPeers = config.getPeers();
 			
-			for (String sw : switches){
-				log.debug("Switchjoin {}", sw);
-			}
+			bgpdAttachmentPoint = new SwitchPort(
+					new Dpid(config.getBgpdAttachmentDpid()),
+					new Port(config.getBgpdAttachmentPort()));
+			//bgpdAttachmentDpid = config.getBgpdAttachmentDpid();
+			//bgpdAttachmentPort = config.getBgpdAttachmentPort();
 			
 		} catch (JsonParseException e) {
 			log.error("Error in JSON file", e);
@@ -149,14 +220,40 @@
 	    
 	    ptree = new Ptree(32);
 	    
-	    routerIpAddresses = new HashSet<InetAddress>();
+	    //routerIpAddresses = new HashSet<InetAddress>();
 		
 		// Register floodlight provider and REST handler.
 		floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
 		topology = context.getServiceImpl(ITopologyService.class);
 		topoRouteService = context.getServiceImpl(ITopoRouteService.class);
 		devices = context.getServiceImpl(IDeviceService.class);
-		restApi = context.getServiceImpl(IRestApiService.class);		
+		restApi = context.getServiceImpl(IRestApiService.class);
+		
+		//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);
+		
+		/*
+		linkStorage = new LinkStorageImpl();
+		//XXX Hack to pull out the database location from NetworkGraphPublisher's config
+		String databaseConfig = null;
+		for (IFloodlightModule fm : context.getAllModules()){
+			if (fm instanceof NetworkGraphPublisher){
+				Map<String, String> configMap = context.getConfigParams(fm);
+				databaseConfig = configMap.get("dbconf");
+				break;
+			}
+		}	
+		if (databaseConfig == null){
+			log.error("Couldn't find database config string \"dbconf\"");
+			System.exit(1);
+		}
+		linkStorage.init(databaseConfig);
+		*/
+		//linkUpdates = new ConcurrentSkipListSet<ILinkDiscovery.LDUpdate>();
+		linkUpdates = new ArrayList<LDUpdate>();
+		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
+		topologyChangeDetectorTask = new SingletonTask(executor, new TopologyChangeDetector());
 		
 		//Read in config values
 		bgpdRestIp = context.getConfigParams(this).get("BgpdRestIp");
@@ -407,9 +504,6 @@
 	        
 	        match.setDataLayerSource(ingressRouter.getRouterMac().toBytes());
 	        match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_SRC);
-	        
-	        //match.setDataLayerDestination(ingressRouter.getSdnRouterMac().toBytes());
-	        //match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_DST);
 
 	        InetAddress address = null;
 	        try {
@@ -429,7 +523,7 @@
 	        
 	        //Set up output action
 	        OFActionOutput outputAction = new OFActionOutput();
-	        outputAction.setMaxLength((short)0xffff); //TODO check what this is (and if needed for mac rewrite)
+	        outputAction.setMaxLength((short)0xffff);
 	        
 	        Port outputPort = shortestPath.flowEntries().get(0).outPort();
 	        outputAction.setPort(outputPort.value());
@@ -502,9 +596,6 @@
 	        match.setDataLayerSource(ingressRouter.getRouterMac().toBytes());
 	        match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_SRC);
 	        
-	        //match.setDataLayerDestination(ingressRouter.getSdnRouterMac().toBytes());
-	        //match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_DST);
-
 	        InetAddress address = null;
 	        try {
 				address = InetAddress.getByAddress(node.key);
@@ -651,11 +742,138 @@
 		}
 	}
 	
+	private void setupBgpPaths(){
+		for (BgpPeer bgpPeer : bgpPeers){
+			Interface peerInterface = interfaces.get(bgpPeer.getInterfaceName());
+			
+			DataPath path = topoRouteService.getShortestPath(
+					peerInterface.getSwitchPort(), bgpdAttachmentPoint);
+			
+			if (path == null){
+				log.debug("Unable to compute path for BGP traffic for {}",
+							bgpPeer.getIpAddress());
+				continue;
+			}
+			
+			//Set up the flow mod
+			OFFlowMod fm =
+	                (OFFlowMod) floodlightProvider.getOFMessageFactory()
+	                                              .getMessage(OFType.FLOW_MOD);
+			
+	        OFActionOutput action = new OFActionOutput();
+	        action.setMaxLength((short)0xffff);
+	        List<OFAction> actions = new ArrayList<OFAction>();
+	        actions.add(action);
+	        
+	        fm.setIdleTimeout((short)0)
+	        .setHardTimeout((short)0)
+	        .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+	        .setCookie(BGP_COOKIE)
+	        .setCommand(OFFlowMod.OFPFC_ADD)
+	        .setPriority(SDNIP_PRIORITY)
+	        .setActions(actions)
+	        .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
+
+	        //Forward = gateway -> bgpd, reverse = bgpd -> gateway
+	        OFMatch forwardMatchSrc = new OFMatch();
+	        
+	        
+	        String interfaceCidrAddress = peerInterface.getIpAddress().getHostAddress() 
+	        					+ "/32";
+	        String peerCidrAddress = bgpPeer.getIpAddress().getHostAddress()
+	        					+ "/32";
+	        	        
+	        //Common match fields
+	        forwardMatchSrc.setDataLayerType(Ethernet.TYPE_IPv4);
+	        //forwardMatch.setWildcards(forwardMatch.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
+	        forwardMatchSrc.setNetworkProtocol(IPv4.PROTOCOL_TCP);
+	        forwardMatchSrc.setTransportDestination(BGP_PORT);
+	        forwardMatchSrc.setWildcards(forwardMatchSrc.getWildcards() & ~OFMatch.OFPFW_IN_PORT
+	        				& ~OFMatch.OFPFW_DL_TYPE & ~OFMatch.OFPFW_NW_PROTO);
+	        
+	        
+	        OFMatch reverseMatchSrc = forwardMatchSrc.clone();
+	        
+	        forwardMatchSrc.setFromCIDR(peerCidrAddress, OFMatch.STR_NW_SRC);
+	        forwardMatchSrc.setFromCIDR(interfaceCidrAddress, OFMatch.STR_NW_DST);
+	        
+	        OFMatch forwardMatchDst = forwardMatchSrc.clone();
+	        
+	        forwardMatchSrc.setTransportSource(BGP_PORT);
+	        forwardMatchSrc.setWildcards(forwardMatchSrc.getWildcards() & ~OFMatch.OFPFW_TP_SRC);
+	        forwardMatchDst.setTransportDestination(BGP_PORT);
+	        forwardMatchDst.setWildcards(forwardMatchDst.getWildcards() & ~OFMatch.OFPFW_TP_DST);
+	        
+	        reverseMatchSrc.setFromCIDR(interfaceCidrAddress, OFMatch.STR_NW_SRC);
+	        reverseMatchSrc.setFromCIDR(peerCidrAddress, OFMatch.STR_NW_DST);
+	        
+	        OFMatch reverseMatchDst = reverseMatchSrc.clone();
+	        
+	        reverseMatchSrc.setTransportSource(BGP_PORT);
+	        reverseMatchSrc.setWildcards(forwardMatchSrc.getWildcards() & ~OFMatch.OFPFW_TP_SRC);
+	        reverseMatchDst.setTransportDestination(BGP_PORT);
+	        reverseMatchDst.setWildcards(forwardMatchDst.getWildcards() & ~OFMatch.OFPFW_TP_DST);
+	        
+	        fm.setMatch(forwardMatchSrc);
+	        
+			for (FlowEntry flowEntry : path.flowEntries()){
+				OFFlowMod forwardFlowModSrc, forwardFlowModDst;
+				OFFlowMod reverseFlowModSrc, reverseFlowModDst;
+				try {
+					forwardFlowModSrc = fm.clone();
+					forwardFlowModDst = fm.clone();
+					reverseFlowModSrc = fm.clone();
+					reverseFlowModDst = fm.clone();
+				} catch (CloneNotSupportedException e) {
+					log.warn("Clone failed", e);
+					continue;
+				}
+				
+				forwardMatchSrc.setInputPort(flowEntry.inPort().value());
+				forwardFlowModSrc.setMatch(forwardMatchSrc);
+				((OFActionOutput)forwardFlowModSrc.getActions().get(0))
+						.setPort(flowEntry.outPort().value());
+				
+				forwardMatchDst.setInputPort(flowEntry.inPort().value());
+				forwardFlowModDst.setMatch(forwardMatchDst);
+				((OFActionOutput)forwardFlowModDst.getActions().get(0))
+						.setPort(flowEntry.outPort().value());
+				
+				reverseMatchSrc.setInputPort(flowEntry.outPort().value());
+				reverseFlowModSrc.setMatch(reverseMatchSrc);
+				((OFActionOutput)reverseFlowModSrc.getActions().get(0))
+						.setPort(flowEntry.inPort().value());
+				
+				reverseMatchDst.setInputPort(flowEntry.outPort().value());
+				reverseFlowModDst.setMatch(reverseMatchDst);
+				((OFActionOutput)reverseFlowModDst.getActions().get(0))
+						.setPort(flowEntry.inPort().value());
+				
+				IOFSwitch sw = floodlightProvider.getSwitches().get(flowEntry.dpid().value());
+				
+				//Hopefully the switch is there
+				List<OFMessage> msgList = new ArrayList<OFMessage>(2);
+				msgList.add(forwardFlowModSrc);
+				msgList.add(forwardFlowModDst);
+				msgList.add(reverseFlowModSrc);
+				msgList.add(reverseFlowModDst);
+				
+				try {
+					sw.write(msgList, null);
+					sw.flush();
+				} catch (IOException e) {
+					log.error("Failure writing flow mod", e);
+				}
+			}
+		}
+	}
 	
 	private void beginRouting(){
 		log.debug("Topology is now ready, beginning routing function");
+		setupBgpPaths();
+		setupFullMesh();
 		
-		//traverse ptree and create flows for all routes
+		//Traverse ptree and create flows for all routes
 		for (PtreeNode node = ptree.begin(); node != null; node = ptree.next(node)){
 			if (node.rib != null){
 				prefixAdded(node);
@@ -718,6 +936,8 @@
 		floodlightProvider.addOFSwitchListener(this);
 		topology.addListener(this);
 		
+		floodlightProvider.addOFMessageListener(OFType.PACKET_IN, proxyArp);
+		
 		//Retrieve the RIB from BGPd during startup
 		retrieveRib();
 	}
@@ -734,16 +954,41 @@
 				//They happen way too frequently (may be a bug in our link discovery)
 				refreshNeeded = true;
 			}
+			
 			log.debug("Topo change {}", ldu.getOperation());
+			/*
+			if (ldu.getOperation().equals(ILinkDiscovery.UpdateOperation.LINK_ADDED)){
+				log.debug("Link Added: src={} outPort={} dst={} inPort={}",
+						new Object[] {
+						HexString.toHexString(ldu.getSrc()), ldu.getSrcPort(),
+						HexString.toHexString(ldu.getDst()), ldu.getDstPort()});
+				TopoLinkServiceImpl impl = new TopoLinkServiceImpl();
+				
+				List<Link> retval = impl.getActiveLinks();
+				
+				log.debug("retval size {}", retval.size());
+				
+				for (Link l : retval){
+					log.debug("link {}", l);
+				}
+			}
+			*/
+			if (ldu.getOperation().equals(ILinkDiscovery.UpdateOperation.LINK_ADDED)){
+				synchronized (linkUpdates) {
+					linkUpdates.add(ldu);
+				}
+			}
 		}
 		
 		if (refreshNeeded){
-			if (topologyReady){
+			topologyChangeDetectorTask.reschedule(TOPO_DETECTION_WAIT, TimeUnit.SECONDS);
+			/*if (topologyReady){
 				setupFullMesh();
 			}
 			else{
 				checkStatus();
-			}
+			}*/
+			
 		}
 	}
 
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java
index 5194584..65617c8 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java
@@ -4,15 +4,38 @@
 import java.util.Map;
 
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.openflow.util.HexString;
 
 public class Configuration {
-	List<String> switches;
-	Map<String, GatewayRouter> gateways;
+	private long bgpdAttachmentDpid;
+	private short bgpdAttachmentPort;
+	private List<String> switches;
+	private Map<String, Interface> interfaces;
+	private List<BgpPeer> peers;
+	private Map<String, GatewayRouter> gateways;
 	
 	public Configuration() {
 		// TODO Auto-generated constructor stub
 	}
 
+	public long getBgpdAttachmentDpid() {
+		return bgpdAttachmentDpid;
+	}
+
+	@JsonProperty("bgpdAttachmentDpid")
+	public void setBgpdAttachmentDpid(String bgpdAttachmentDpid) {
+		this.bgpdAttachmentDpid = HexString.toLong(bgpdAttachmentDpid);
+	}
+
+	public short getBgpdAttachmentPort() {
+		return bgpdAttachmentPort;
+	}
+
+	@JsonProperty("bgpdAttachmentPort")
+	public void setBgpdAttachmentPort(short bgpdAttachmentPort) {
+		this.bgpdAttachmentPort = bgpdAttachmentPort;
+	}
+
 	public List<String> getSwitches() {
 		return switches;
 	}
@@ -22,6 +45,24 @@
 		this.switches = switches;
 	}
 
+	public Map<String, Interface> getInterfaces() {
+		return interfaces;
+	}
+
+	@JsonProperty("interfaces")
+	public void setInterfaces(Map<String, Interface> interfaces) {
+		this.interfaces = interfaces;
+	}
+	
+	public List<BgpPeer> getPeers() {
+		return peers;
+	}
+
+	@JsonProperty("bgpPeers")
+	public void setPeers(List<BgpPeer> peers) {
+		this.peers = peers;
+	}
+
 	public Map<String, GatewayRouter> getGateways() {
 		return gateways;
 	}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/GatewayRouter.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/GatewayRouter.java
index 4016c69..e893acf 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/GatewayRouter.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/GatewayRouter.java
@@ -15,6 +15,7 @@
 	private short port;
 	private MACAddress routerMac;
 	private IPv4 routerIp;
+	private IPv4 myIpAddress;
 	
 	
 	public SwitchPort getAttachmentPoint() {
@@ -59,4 +60,13 @@
 	public void setRouterIp(String routerIp) {
 		this.routerIp = new IPv4(routerIp);
 	}
+	
+	public IPv4 getMyIpAddress() {
+		return myIpAddress;
+	}
+	
+	@JsonProperty("myIpAddress")
+	public void setMyIpAddress(String myIpAddress) {
+		this.myIpAddress = new IPv4(myIpAddress);
+	}
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Interface.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Interface.java
new file mode 100644
index 0000000..15b2125
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Interface.java
@@ -0,0 +1,63 @@
+package net.onrc.onos.ofcontroller.bgproute;
+
+import java.net.InetAddress;
+
+import net.onrc.onos.ofcontroller.util.Dpid;
+import net.onrc.onos.ofcontroller.util.Port;
+import net.onrc.onos.ofcontroller.util.SwitchPort;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.openflow.util.HexString;
+
+import com.google.common.net.InetAddresses;
+
+public class Interface {
+	private SwitchPort switchPort = null;
+	private long dpid;
+	private short port;
+	private InetAddress ipAddress;
+	private int prefixLength;
+	
+	public synchronized SwitchPort getSwitchPort() {
+		if (switchPort == null){
+			switchPort = new SwitchPort(new Dpid(dpid), new Port(port));
+		}
+		return switchPort;
+	}
+	
+	public long getDpid() {
+		return dpid;
+	}
+
+	@JsonProperty("dpid")
+	public void setDpid(String dpid) {
+		this.dpid = HexString.toLong(dpid);
+	}
+
+	public short getPort() {
+		return port;
+	}
+
+	@JsonProperty("port")
+	public void setPort(short port) {
+		this.port = port;
+	}
+
+	public InetAddress getIpAddress() {
+		return ipAddress;
+	}
+
+	@JsonProperty("ipAddress")
+	public void setIpAddress(String ipAddress) {
+		this.ipAddress = InetAddresses.forString(ipAddress);
+	}
+
+	public int getPrefixLength() {
+		return prefixLength;
+	}
+
+	@JsonProperty("prefixLength")
+	public void setPrefixLength(int prefixLength) {
+		this.prefixLength = prefixLength;
+	}
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpTableEntry.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpTableEntry.java
new file mode 100644
index 0000000..5830cfd
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ArpTableEntry.java
@@ -0,0 +1,27 @@
+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/ProxyArpManager.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
new file mode 100644
index 0000000..b7fc896
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
@@ -0,0 +1,274 @@
+package net.onrc.onos.ofcontroller.proxyarp;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.util.MACAddress;
+
+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;
+
+public class ProxyArpManager implements IOFMessageListener {
+	private static Logger log = LoggerFactory.getLogger(ProxyArpManager.class);
+	
+	private final long ARP_ENTRY_TIMEOUT = 600000; //ms (== 5 mins)
+	
+	protected IFloodlightProviderService floodlightProvider;
+	protected ITopologyService topology;
+	
+	
+	protected Map<InetAddress, ArpTableEntry> arpTable;
+	
+	public ProxyArpManager(IFloodlightProviderService floodlightProvider,
+				ITopologyService topology){
+		this.floodlightProvider = floodlightProvider;
+		this.topology = topology;
+		
+		arpTable = new HashMap<InetAddress, ArpTableEntry>();
+	}
+	
+	@Override
+	public String getName() {
+		return  "ProxyArpManager";
+	}
+
+	@Override
+	public boolean isCallbackOrderingPrereq(OFType type, String name) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	@Override
+	public boolean isCallbackOrderingPostreq(OFType type, String name) {
+		// TODO Auto-generated method stub
+		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);
+		
+		if (eth.getEtherType() == Ethernet.TYPE_ARP){
+			
+			
+			ARP arp = (ARP) eth.getPayload();
+			
+			if (arp.getOpCode() == ARP.OP_REQUEST) {
+				log.debug("ARP request received for {}", bytesToStringAddr(arp.getTargetProtocolAddress()));
+				
+				byte[] mac = lookupArpTable(arp.getTargetProtocolAddress());
+				
+				if (mac == null){
+					//Mac address is not in our arp table. 
+					//We need to flood the request out edge ports
+					Set<NodePortTuple> broadcastPorts = topology.getBroadcastDomainPorts();
+					log.debug("size {}", broadcastPorts.size());
+					for (NodePortTuple nodePort : broadcastPorts){
+						log.debug("Port {}", nodePort);
+					}
+					broadcastArpRequestOutEdge(pi, sw.getId(), pi.getInPort());
+				}
+				else {
+					//We know the address, so send a reply
+					log.debug("Sending reply of {}", MACAddress.valueOf(mac).toString());
+					sendArpReply(arp, pi, mac, sw);
+				}
+			}
+			else if (arp.getOpCode() == ARP.OP_REPLY) {
+				log.debug("ARP reply recieved for {}", bytesToStringAddr(arp.getSenderProtocolAddress()));
+				
+				log.debug("arp table {}", arpTable.keySet());
+				
+				updateArpTable(arp);
+			}
+		}
+		
+		
+		return Command.CONTINUE;
+	}
+
+	private synchronized byte[] lookupArpTable(byte[] ipAddress){
+		InetAddress addr;
+		try {
+			addr = InetAddress.getByAddress(ipAddress);
+		} catch (UnknownHostException e) {
+			log.warn("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
+			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.warn("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()));
+		}
+	}
+	
+	private void broadcastArpRequestOutEdge(OFPacketIn pi, long inSwitch, short inPort){
+		for (IOFSwitch sw : floodlightProvider.getSwitches().values()){
+			Collection<Short> enabledPorts = sw.getEnabledPortNumbers();
+			Set<Short> linkPorts = topology.getPortsWithLinks(sw.getId());
+			
+			if (linkPorts == null){
+				//I think this means the switch isn't known to topology yet.
+				//Maybe it only just joined.
+				continue;
+			}
+			
+			OFPacketOut po = new OFPacketOut();
+			po.setInPort(OFPort.OFPP_NONE)
+				.setBufferId(-1)
+				//.setLengthU(OFActionOutput.MINIMUM_LENGTH);
+				.setPacketData(pi.getPacketData());
+				
+			List<OFAction> actions = new ArrayList<OFAction>();
+			
+			for (short portNum : enabledPorts){
+				//log.debug("linkPorts {}", linkPorts);
+				//log.debug("portNum {}", portNum);
+				if (linkPorts.contains(portNum) || 
+						(sw.getId() == inSwitch && portNum == inPort)){
+					//If this port isn't an edge port or is the ingress port
+					//for the ARP, don't broadcast out it
+					continue;
+				}
+				
+				actions.add(new OFActionOutput(portNum));
+				log.debug("Broadcasting out {}/{}", HexString.toHexString(sw.getId()), portNum);
+			}
+			
+			po.setActions(actions);
+			short actionsLength = (short) (actions.size() * OFActionOutput.MINIMUM_LENGTH);
+			po.setActionsLength(actionsLength);
+			po.setLengthU(OFPacketOut.MINIMUM_LENGTH + actionsLength 
+					+ pi.getPacketData().length);
+			
+			List<OFMessage> msgList = new ArrayList<OFMessage>();
+			msgList.add(po);
+			
+			try {
+				sw.write(msgList, null);
+				sw.flush();
+			} catch (IOException e) {
+				log.error("Failure writing packet out to switch", e);
+			}
+		}
+	}
+	
+	private void sendArpReply(ARP arpRequest, OFPacketIn pi, byte[] macRequested, IOFSwitch sw){
+		ARP arpReply = new ARP();
+		arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
+			.setProtocolType(ARP.PROTO_TYPE_IP)
+			.setHardwareAddressLength((byte)Ethernet.DATALAYER_ADDRESS_LENGTH)
+			.setProtocolAddressLength((byte)4) //can't find the constant anywhere
+			.setOpCode(ARP.OP_REPLY)
+			.setSenderHardwareAddress(macRequested)
+			.setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
+			.setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
+			.setTargetProtocolAddress(arpRequest.getSenderProtocolAddress());
+		
+		Ethernet eth = new Ethernet();
+		eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
+			.setSourceMACAddress(macRequested)
+			.setEtherType(Ethernet.TYPE_ARP)
+			.setPayload(arpReply);
+		
+		List<OFAction> actions = new ArrayList<OFAction>();
+		actions.add(new OFActionOutput(pi.getInPort()));
+		
+		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);
+		
+		try {
+			log.debug("Sending ARP reply to {}/{}", HexString.toHexString(sw.getId()), pi.getInPort());
+			sw.write(msgList, null);
+			sw.flush();
+		} catch (IOException e) {
+			log.warn("Failure writing packet out to switch", e);
+		}
+	}
+	
+	private String bytesToStringAddr(byte[] bytes){
+		InetAddress addr;
+		try {
+			addr = InetAddress.getByAddress(bytes);
+		} catch (UnknownHostException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+			return "";
+		}
+		if (addr == null) return "";
+		else return addr.getHostAddress();
+	}
+}