Intial implementation of a proxy arp module to support the SDN-IP application
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 d3bb598..244c533 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
@@ -26,6 +26,7 @@
 import net.onrc.onos.ofcontroller.core.INetMapTopologyService.ITopoRouteService;
 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;
@@ -63,6 +64,8 @@
 	protected IDeviceService devices;
 	protected IRestApiService restApi;
 	
+	protected ProxyArpManager proxyArp;
+	
 	protected static Ptree ptree;
 	protected String bgpdRestIp;
 	protected String routerId;
@@ -168,7 +171,11 @@
 		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);
 		
 		//Read in config values
 		bgpdRestIp = context.getConfigParams(this).get("BgpdRestIp");
@@ -850,6 +857,8 @@
 		floodlightProvider.addOFSwitchListener(this);
 		topology.addListener(this);
 		
+		floodlightProvider.addOFMessageListener(OFType.PACKET_IN, proxyArp);
+		
 		//Retrieve the RIB from BGPd during startup
 		retrieveRib();
 	}
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..1f852c8
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
@@ -0,0 +1,251 @@
+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 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.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");
+				
+				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
+					sendArpReply(arp, pi, mac, sw);
+				}
+			}
+			else if (arp.getOpCode() == ARP.OP_REPLY) {
+				log.debug("ARP reply recieved for {}");
+				
+				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());
+			
+			
+			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));
+			}
+			
+			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(pi.getPacketData())
+			.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 {
+			sw.write(msgList, null);
+			sw.flush();
+		} catch (IOException e) {
+			log.warn("Failure writing packet out to switch", e);
+		}
+	}
+}