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);
+ }
+ }
+}