Renamed SDN-IP packages and classes.

The code use to use the name 'BgpRoute' in a number of places, which is not
descriptive and doesn't map to how we talk about SDN-IP (we always call it
SDN-IP in all other documents/presentations).

Details of changes are as follows:

net.onrc.onos.apps.bgproute -> net.onrc.onos.apps.sdnip
    BgpRoute.java -> SdnIp.java
    IBgpRouteService.java -> ISdnIpService.java

created new package for web classes: net.onrc.onos.apps.sdnip.web
    BgpRouteResource.java -> IncomingRequestResource.java
    BgpRouteResourceSynch.java -> OutgoingRequestResource.java
    BgpRouteWebRoutable.java -> SdnIpWebRoutable.java

Change-Id: Ie6b1cbe4e95736d4cbd53b9f4def7cc3e0b46132
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/BgpPeer.java b/src/main/java/net/onrc/onos/apps/sdnip/BgpPeer.java
new file mode 100644
index 0000000..9c93d9e
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/BgpPeer.java
@@ -0,0 +1,47 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.net.InetAddress;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import com.google.common.net.InetAddresses;
+
+/**
+ * Configuration details for a BGP peer. It contains the peer's IP address and
+ * an interface name which maps to the interface they are attached at.
+ */
+public class BgpPeer {
+    private final String interfaceName;
+    private final InetAddress ipAddress;
+
+    /**
+     * Class constructor, taking the interface name and IP address of the peer.
+     *
+     * @param interfaceName the String name of the interface which can be used
+     * to look up the interface this peer is attached at
+     * @param ipAddress the IP address of the peer as a String
+     */
+    public BgpPeer(@JsonProperty("interface") String interfaceName,
+                   @JsonProperty("ipAddress") String ipAddress) {
+        this.interfaceName = interfaceName;
+        this.ipAddress = InetAddresses.forString(ipAddress);
+    }
+
+    /**
+     * Gets the interface name.
+     *
+     * @return the interface name as a String
+     */
+    public String getInterfaceName() {
+        return interfaceName;
+    }
+
+    /**
+     * Gets the IP address of the peer.
+     *
+     * @return the IP address as an {@link InetAddress} object
+     */
+    public InetAddress getIpAddress() {
+        return ipAddress;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/Configuration.java b/src/main/java/net/onrc/onos/apps/sdnip/Configuration.java
new file mode 100644
index 0000000..28f195c
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/Configuration.java
@@ -0,0 +1,169 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.util.Collections;
+import java.util.List;
+
+import net.floodlightcontroller.util.MACAddress;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.openflow.util.HexString;
+
+/**
+ * Contains the configuration data for SDN-IP that has been read from a
+ * JSON-formatted configuration file.
+ */
+public class Configuration {
+    private long bgpdAttachmentDpid;
+    private short bgpdAttachmentPort;
+    private MACAddress bgpdMacAddress;
+    private short vlan;
+    private List<String> switches;
+    private List<Interface> interfaces;
+    private List<BgpPeer> peers;
+
+    /**
+     * Default constructor.
+     */
+    public Configuration() {
+        // TODO Auto-generated constructor stub
+    }
+
+    /**
+     * Gets the switch that BGPd is attached to.
+     *
+     * @return the dpid of BGPd's attachment point
+     */
+    public long getBgpdAttachmentDpid() {
+        return bgpdAttachmentDpid;
+    }
+
+    /**
+     * Sets the switch that BGPd is attached to.
+     *
+     * @param bgpdAttachmentDpid the dpid of BGPd's attachment point
+     */
+    @JsonProperty("bgpdAttachmentDpid")
+    public void setBgpdAttachmentDpid(String bgpdAttachmentDpid) {
+        this.bgpdAttachmentDpid = HexString.toLong(bgpdAttachmentDpid);
+    }
+
+    /**
+     * Gets the port that BGPd is attached to.
+     *
+     * @return the port number on the switch where BGPd is attached
+     */
+    public short getBgpdAttachmentPort() {
+        return bgpdAttachmentPort;
+    }
+
+    /**
+     * Sets the port that BGPd is attached to.
+     *
+     * @param bgpdAttachmentPort the port number on the switch where BGPd is
+     * attached
+     */
+    @JsonProperty("bgpdAttachmentPort")
+    public void setBgpdAttachmentPort(short bgpdAttachmentPort) {
+        this.bgpdAttachmentPort = bgpdAttachmentPort;
+    }
+
+    /**
+     * Gets the MAC address of the BGPd host interface.
+     *
+     * @return the MAC address
+     */
+    public MACAddress getBgpdMacAddress() {
+        return bgpdMacAddress;
+    }
+
+    /**
+     * Sets the MAC address of the BGPd host interface.
+     *
+     * @param strMacAddress the MAC address
+     */
+    @JsonProperty("bgpdMacAddress")
+    public void setBgpdMacAddress(String strMacAddress) {
+        this.bgpdMacAddress = MACAddress.valueOf(strMacAddress);
+    }
+
+    /**
+     * Gets a list of the DPIDs of all switches in the system. The DPIDs are
+     * in String format represented in hexadecimal.
+     *
+     * @return the list of DPIDs
+     */
+    public List<String> getSwitches() {
+        return Collections.unmodifiableList(switches);
+    }
+
+    /**
+     * Sets a list of DPIDs of all switches in the system.
+     *
+     * @param switches the list of DPIDs
+     */
+    @JsonProperty("switches")
+    public void setSwitches(List<String> switches) {
+        this.switches = switches;
+    }
+
+    /**
+     * Gets the VLAN number of the VLAN the system is running in, if any.
+     * 0 means we are not running in a VLAN.
+     *
+     * @return the VLAN number if we are running in a VLAN, otherwise 0
+     */
+    public short getVlan() {
+        return vlan;
+    }
+
+    /**
+     * Sets the VLAN number of the VLAN the system is running in.
+     *
+     * @param vlan the VLAN number
+     */
+    @JsonProperty("vlan")
+    public void setVlan(short vlan) {
+        this.vlan = vlan;
+    }
+
+    /**
+     * Gets a list of interfaces in the system, represented by
+     * {@link Interface} objects.
+     *
+     * @return the list of interfaces
+     */
+    public List<Interface> getInterfaces() {
+        return Collections.unmodifiableList(interfaces);
+    }
+
+    /**
+     * Sets a list of interfaces in the system.
+     *
+     * @param interfaces the list of interfaces
+     */
+    @JsonProperty("interfaces")
+    public void setInterfaces(List<Interface> interfaces) {
+        this.interfaces = interfaces;
+    }
+
+    /**
+     * Gets a list of BGP peers we are configured to peer with. Peers are
+     * represented by {@link BgpPeer} objects.
+     *
+     * @return the list of BGP peers
+     */
+    public List<BgpPeer> getPeers() {
+        return Collections.unmodifiableList(peers);
+    }
+
+    /**
+     * Sets a list of BGP peers we are configured to peer with.
+     *
+     * @param peers the list of BGP peers
+     */
+    @JsonProperty("bgpPeers")
+    public void setPeers(List<BgpPeer> peers) {
+        this.peers = peers;
+    }
+
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/FlowCache.java b/src/main/java/net/onrc/onos/apps/sdnip/FlowCache.java
new file mode 100644
index 0000000..8c518d7
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/FlowCache.java
@@ -0,0 +1,164 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPort;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FlowCache {
+    private static final Logger log = LoggerFactory.getLogger(FlowCache.class);
+
+    private final IFloodlightProviderService floodlightProvider;
+
+    private final Map<Long, List<OFFlowMod>> flowCacheMap;
+
+    private final Comparator<OFFlowMod> cookieComparator =
+            new Comparator<OFFlowMod>() {
+        @Override
+        public int compare(OFFlowMod fm1, OFFlowMod fm2) {
+            long difference = fm2.getCookie() - fm1.getCookie();
+
+            if (difference > 0) {
+                return 1;
+            } else if (difference < 0) {
+                return -1;
+            } else {
+                return 0;
+            }
+        }
+    };
+
+    public FlowCache(IFloodlightProviderService floodlightProvider) {
+        this.floodlightProvider = floodlightProvider;
+
+        flowCacheMap = new HashMap<Long, List<OFFlowMod>>();
+    }
+
+    public void write(long dpid, OFFlowMod flowMod) {
+        synchronized (this) {
+            List<OFFlowMod> flowModList = new ArrayList<OFFlowMod>(1);
+            flowModList.add(flowMod);
+            write(dpid, flowModList);
+        }
+    }
+
+    public void write(long dpid, List<OFFlowMod> flowMods) {
+        synchronized (this) {
+            ensureCacheForSwitch(dpid);
+
+            List<OFFlowMod> clones = new ArrayList<OFFlowMod>(flowMods.size());
+
+            // Somehow the OFFlowMods we get passed in will change later on.
+            // No idea how this happens, but we can just clone to prevent problems
+            try {
+                for (OFFlowMod fm : flowMods) {
+                    clones.add(fm.clone());
+                }
+            } catch (CloneNotSupportedException e) {
+                log.debug("Clone exception", e);
+            }
+
+            flowCacheMap.get(dpid).addAll(clones);
+
+            IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+
+            if (sw == null) {
+                log.debug("Switch not found when writing flow mods");
+                return;
+            }
+
+            List<OFMessage> msgList = new ArrayList<OFMessage>(clones.size());
+            msgList.addAll(clones);
+
+            try {
+                sw.write(msgList, null);
+            } catch (IOException e) {
+                log.error("Error writing to switch", e);
+            }
+        }
+    }
+
+    public void delete(long dpid, OFFlowMod flowMod) {
+        synchronized (this) {
+            List<OFFlowMod> flowModList = new ArrayList<OFFlowMod>(1);
+            flowModList.add(flowMod);
+            delete(dpid, flowModList);
+        }
+    }
+
+    public void delete(long dpid, List<OFFlowMod> flowMods) {
+        synchronized (this) {
+            ensureCacheForSwitch(dpid);
+
+            // Remove the flow mods from the cache first before we alter them
+            flowCacheMap.get(dpid).removeAll(flowMods);
+
+            // Alter the original flow mods to make them delete flow mods
+            for (OFFlowMod fm : flowMods) {
+                fm.setCommand(OFFlowMod.OFPFC_DELETE_STRICT)
+                        .setOutPort(OFPort.OFPP_NONE)
+                        .setLengthU(OFFlowMod.MINIMUM_LENGTH);
+
+                fm.getActions().clear();
+            }
+
+            IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+            if (sw == null) {
+                log.debug("Switch not found when writing flow mods");
+                return;
+            }
+
+            List<OFMessage> msgList = new ArrayList<OFMessage>(flowMods.size());
+            msgList.addAll(flowMods);
+
+            try {
+                sw.write(msgList, null);
+            } catch (IOException e) {
+                log.error("Error writing to switch", e);
+            }
+        }
+    }
+
+    public void switchConnected(IOFSwitch sw) {
+        synchronized (this) {
+            log.debug("Switch connected: {}", sw);
+
+            ensureCacheForSwitch(sw.getId());
+
+            List<OFFlowMod> flowMods = flowCacheMap.get(sw.getId());
+
+            Collections.sort(flowMods, cookieComparator);
+
+            sw.clearAllFlowMods();
+
+            List<OFMessage> messages = new ArrayList<OFMessage>(flowMods.size());
+            messages.addAll(flowMods);
+
+            try {
+                sw.write(messages, null);
+            } catch (IOException e) {
+                log.error("Failure writing flow mods to switch {}",
+                        HexString.toHexString(sw.getId()));
+            }
+        }
+    }
+
+    private void ensureCacheForSwitch(long dpid) {
+        if (!flowCacheMap.containsKey(dpid)) {
+            flowCacheMap.put(dpid, new ArrayList<OFFlowMod>());
+        }
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/IPatriciaTree.java b/src/main/java/net/onrc/onos/apps/sdnip/IPatriciaTree.java
new file mode 100644
index 0000000..fa548b2
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/IPatriciaTree.java
@@ -0,0 +1,100 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.util.Iterator;
+
+/**
+ * A PATRICIA tree is data structure for storing data where entries aggregate
+ * based on shared prefixes. They provide lookups in O(k) where k is the
+ * maximum length of the strings in the tree. They work well for storing IP
+ * addresses.
+ * <p/>
+ * SDN-IP uses a patricia tree to store routes learnt from BGPd. BGPd sends
+ * route updates in the form:
+ * {@code <prefix, next_hop>},
+ * e.g. {@code <192.168.1.0/24, 10.0.0.1>}
+ * <p/>
+ * These updates are stored in the patricia tree, which acts as a map from
+ * {@code prefix} to {@code next_hop}. {@code next_hop} values can be looked up
+ * by prefix.
+ *
+ * @param <V> The class of the data to stored in the patricia tree
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Patricia_tree">Patricia tree</a>
+ */
+public interface IPatriciaTree<V> {
+    /**
+     * Puts a new mapping into the patricia tree.
+     *
+     * @param prefix the Prefix which is the key for this entry
+     * @param value the value that maps to the Prefix
+     * @return the old value that was mapped to the Prefix, or null if there
+     * was no such mapping
+     */
+    public V put(Prefix prefix, V value);
+
+    /**
+     * Searches the tree for a prefix that exactly matches the argument. If an
+     * exact match for the prefix is found in the tree, the value it maps to is
+     * returned. Otherwise, null is returned.
+     *
+     * @param prefix the prefix to look up in the tree
+     * @return the value if the prefix was found, otherwise null
+     */
+    public V lookup(Prefix prefix);
+
+    /**
+     * Searches the tree for the closest containing prefix of the supplied
+     * argument. If an exact match is found, that will be returned. Otherwise,
+     * the value of the most specific prefix that contains the argument prefix
+     * will be returned. If no such prefix is found, null is returned.
+     *
+     * @param prefix the prefix to find the closest containing match for in the
+     * tree
+     * @return the value of the match if one was found, otherwise null
+     */
+    public V match(Prefix prefix);
+
+    /**
+     * Removes a prefix to value mapping from the tree. The prefix argument is
+     * first looked up in the same way as the {@link #lookup(Prefix)} method.
+     * If an exact match to the prefix is found in the tree, its value is
+     * is checked to see if it matches the supplied argument value. The prefix
+     * and value will be removed only if both the prefix and value arguments
+     * match a mapping in the tree.
+     *
+     * @param prefix the prefix to remove from the tree
+     * @param value the value that must be mapped to the prefix for it to be
+     * removed
+     * @return true if a mapping was removed, otherwise false
+     */
+    public boolean remove(Prefix prefix, V value);
+
+    /**
+     * Gets an iterator over all mappings in the tree.
+     *
+     * @return an iterator that will iterate over all entries in the tree
+     */
+    public Iterator<Entry<V>> iterator();
+
+    /**
+     * Represents an entry in the patricia tree. The {@code Entry} is a mapping
+     * from {@link Prefix} to a {@link V} value object.
+     *
+     * @param <V> the class of objects stored in the tree
+     */
+    interface Entry<V> {
+        /**
+         * Gets the {@link Prefix} object for this entry.
+         *
+         * @return the prefix for this entry
+         */
+        public Prefix getPrefix();
+
+        /**
+         * Gets the value of this entry.
+         *
+         * @return the value object of this entry
+         */
+        public V getValue();
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/ISdnIpService.java b/src/main/java/net/onrc/onos/apps/sdnip/ISdnIpService.java
new file mode 100644
index 0000000..040afe6
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/ISdnIpService.java
@@ -0,0 +1,48 @@
+package net.onrc.onos.apps.sdnip;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+/**
+ * The API exported by the main SDN-IP class. This is the interface between the
+ * REST handlers and the SDN-IP module.
+ */
+public interface ISdnIpService extends IFloodlightService {
+
+    /**
+     * Gets a reference to SDN-IP's PATRICIA tree which stores the route table.
+     *
+     * XXX This is a poor API because it exposes internal state of SDN-IP.
+     *
+     * @return the PATRICIA tree.
+     */
+    public IPatriciaTree<RibEntry> getPtree();
+
+    /**
+     * Gets the IP address of REST server on the BGPd side. This is used to
+     * communicate with BGPd.
+     *
+     * @return the IP address as a String
+     */
+    public String getBgpdRestIp();
+
+    /**
+     * Gets the router ID, which is sent to BGPd to identify the route table
+     * we're interested in.
+     *
+     * @return the router ID as a String
+     */
+    public String getRouterId();
+
+    /**
+     * Clears SDN-IP's route table.
+     */
+    public void clearPtree();
+
+    /**
+     * Pass a RIB update to the {@link ISdnIpService}.
+     *
+     * @param update a {@link RibUpdate} object containing details of the
+     * update
+     */
+    public void newRibUpdate(RibUpdate update);
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/Interface.java b/src/main/java/net/onrc/onos/apps/sdnip/Interface.java
new file mode 100644
index 0000000..15abb1f
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/Interface.java
@@ -0,0 +1,141 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.net.InetAddress;
+
+import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.Port;
+import net.onrc.onos.core.util.SwitchPort;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.openflow.util.HexString;
+
+import com.google.common.net.InetAddresses;
+
+/**
+ * Represents an interface, which is an external-facing switch port that
+ * connects to another network.
+ *
+ * SDN-IP treats external-facing ports similarly to router ports. Logically, it
+ * assigns an IP address to these ports which is used for communication with
+ * the BGP peers, for example, the BGP peering session. The other peer will be
+ * configured to peer with the IP address (logically) assigned to the
+ * interface. The logical {@code Interface} construct maps on to a physical port in the
+ * data plane, which of course has no notion of IP addresses.
+ *
+ * Each interface has a name, which is a unique identifying String that is used
+ * to reference this interface in the configuration (for example, to map
+ * {@link BgpPeers} to {@code Interfaces}.
+ */
+public class Interface {
+    private final String name;
+    private final long dpid;
+    private final short port;
+    private final InetAddress ipAddress;
+    private final int prefixLength;
+
+    /**
+     * Class constructor used by the JSON library to create an object.
+     *
+     * @param name the name of the interface
+     * @param dpid the dpid of the switch
+     * @param port the port on the switch
+     * @param ipAddress the IP address logically assigned to the interface
+     * @param prefixLength the length of the network prefix of the IP address
+     */
+    @JsonCreator
+    public Interface(@JsonProperty("name") String name,
+                     @JsonProperty("dpid") String dpid,
+                     @JsonProperty("port") short port,
+                     @JsonProperty("ipAddress") String ipAddress,
+                     @JsonProperty("prefixLength") int prefixLength) {
+        this.name = name;
+        this.dpid = HexString.toLong(dpid);
+        this.port = port;
+        this.ipAddress = InetAddresses.forString(ipAddress);
+        this.prefixLength = prefixLength;
+    }
+
+    /**
+     * Gets the name of the interface.
+     *
+     * @return the name of the interface
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Gets the {@link SwitchPort} that this interface maps to.
+     *
+     * @return the switch port
+     */
+    public SwitchPort getSwitchPort() {
+        //TODO SwitchPort, Dpid and Port are mutable, but they could probably
+        //be made immutable which would prevent the need to copy
+        return new SwitchPort(new Dpid(dpid), new Port(port));
+    }
+
+    /**
+     * Gets the DPID of the switch.
+     *
+     * @return the DPID of the switch
+     */
+    public long getDpid() {
+        return dpid;
+    }
+
+    /**
+     * Gets the port number this interface maps to.
+     *
+     * @return the port number
+     */
+    public short getPort() {
+        return port;
+    }
+
+    /**
+     * Gets the IP address which is logically assigned to the switch port.
+     *
+     * @return the IP address
+     */
+    public InetAddress getIpAddress() {
+        return ipAddress;
+    }
+
+    /**
+     * Gets the prefix length of the interface's IP address.
+     *
+     * @return the prefix length
+     */
+    public int getPrefixLength() {
+        return prefixLength;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof Interface)) {
+            return false;
+        }
+
+        Interface otherInterface = (Interface) other;
+
+        //Don't check switchPort as it's comprised of dpid and port
+        return (name.equals(otherInterface.name)) &&
+                (dpid == otherInterface.dpid) &&
+                (port == otherInterface.port) &&
+                (ipAddress.equals(otherInterface.ipAddress)) &&
+                (prefixLength == otherInterface.prefixLength);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 17;
+        hash = 31 * hash + name.hashCode();
+        hash = 31 * hash + (int) (dpid ^ dpid >>> 32);
+        hash = 31 * hash + (int) port;
+        hash = 31 * hash + ipAddress.hashCode();
+        hash = 31 * hash + prefixLength;
+        return hash;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/Path.java b/src/main/java/net/onrc/onos/apps/sdnip/Path.java
new file mode 100644
index 0000000..7772775
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/Path.java
@@ -0,0 +1,132 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link Path} represents paths within a network that forward traffic from
+ * ingress port to egress port. For every {@code next_hop} received in route
+ * updates from BGPd, we need to push forwarding paths from every other
+ * possible ingress port to the egress port connected to the {@code next_hop}.
+ * <p/>
+ * The {@link Path} object doesn't contain lists of hops along the path.
+ * Rather, it contains details about the egress {@link Interface} and
+ * {@code next_hop} IP address. Implicitly, it represents paths from every
+ * other ingress port to the {@Interface}.
+ * <p/>
+ * Once flow mods are pushed to realize the path in the network, the
+ * {@link Path} object will contain a list of pushed flow mods. These are used
+ * if the path ever needs to be deleted.
+ * <p/>
+ * On startup, paths are pushed to all configured BGP peers, on the assumption
+ * that they're likely to advertise routes to us. These paths are permanent
+ * because the list of peers can't currently change at runtime. If we receive
+ * a route for a {@code next_hop} which is not a peer, a temporary path will
+ * be installed. These paths are temporary because they are removed if all
+ * routes that use them are removed.
+ * <p/>
+ * Finally, the {@link Path} object counts references of prefixes that make use
+ * of the path. If the reference count drops to zero as prefixes are deleted,
+ * the path is no longer useful and will be removed from the network.
+ */
+public class Path {
+    private final Interface dstInterface;
+    private final InetAddress dstIpAddress;
+    private int numUsers; // initialized to 0
+
+    private List<PushedFlowMod> flowMods; // initialized to null
+    private boolean permanent; // initialized to false
+
+    /**
+     * Class constructor, taking the destination {@link Interface} and
+     * destination IP address for the path.
+     *
+     * @param dstInterface the destination interface
+     * @param dstIpAddress the destination IP address
+     */
+    public Path(Interface dstInterface, InetAddress dstIpAddress) {
+        this.dstInterface = dstInterface;
+        this.dstIpAddress = dstIpAddress;
+    }
+
+    /**
+     * Gets the destination {@link Interface} of the path.
+     *
+     * @return the destination interface
+     */
+    public Interface getDstInterface() {
+        return dstInterface;
+    }
+
+    /**
+     * Gets the destination IP address.
+     *
+     * @return the destination IP address
+     */
+    public InetAddress getDstIpAddress() {
+        return dstIpAddress;
+    }
+
+    /**
+     * Increments the count of prefixes that use this path.
+     */
+    public void incrementUsers() {
+        numUsers++;
+    }
+
+    /**
+     * Decrements the count of prefixes that use this path.
+     */
+    public void decrementUsers() {
+        numUsers--;
+    }
+
+    /**
+     * Gets the count of prefixes that use this path.
+     *
+     * @return the number of prefixes currently using the path
+     */
+    public int getUsers() {
+        return numUsers;
+    }
+
+    /**
+     * Gets the list of flow mods that were installed to realize this path in
+     * the network.
+     *
+     * @return the list of {@link PushedFlowMods}
+     */
+    public List<PushedFlowMod> getFlowMods() {
+        return Collections.unmodifiableList(flowMods);
+    }
+
+    /**
+     * Sets the list of flow mods that were installed to realize this path in
+     * the network.
+     *
+     * @param flowMods the list of {@link PushedFlowMods}
+     */
+    public void setFlowMods(List<PushedFlowMod> flowMods) {
+        this.flowMods = flowMods;
+    }
+
+    /**
+     * Signifies whether the path is permanent and shouldn't be deleted when
+     * the number of users drops to zero.
+     *
+     * @return true if the path is permanent, false if not
+     */
+    public boolean isPermanent() {
+        return permanent;
+    }
+
+    /**
+     * Set the permanent status of the path to true. Paths are not permanent
+     * by default when constructed, and this method can be used to designate
+     * them as permanent.
+     */
+    public void setPermanent() {
+        permanent = true;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/PatriciaTree.java b/src/main/java/net/onrc/onos/apps/sdnip/PatriciaTree.java
new file mode 100644
index 0000000..844ba35
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/PatriciaTree.java
@@ -0,0 +1,519 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Implements a patricia tree. See {@link IPatriciaTree} for a description of
+ * how the tree works and its usage.
+ *
+ * @param <V> the type of objects that will be stored in the tree
+ */
+public class PatriciaTree<V> implements IPatriciaTree<V> {
+    private final byte[] maskBits = {(byte) 0x00, (byte) 0x80, (byte) 0xc0, (byte) 0xe0, (byte) 0xf0,
+            (byte) 0xf8, (byte) 0xfc, (byte) 0xfe, (byte) 0xff};
+
+    private final int maxPrefixLength;
+
+    private Node top;
+
+    /**
+     * Class constructor which takes the maximum length of strings that can be
+     * stored in the tree. This is used as a sanity check to prevent
+     * excessively long strings from being added, as this could slow down
+     * lookups.
+     *
+     * @param maxPrefixLength the maximum length of prefixes
+     */
+    public PatriciaTree(int maxPrefixLength) {
+        this.maxPrefixLength = maxPrefixLength;
+    }
+
+    @Override
+    public V put(Prefix prefix, V value) {
+        synchronized (this) {
+            if (prefix == null || value == null) {
+                throw new IllegalArgumentException("Null argument");
+            }
+
+            if (prefix.getPrefixLength() > maxPrefixLength) {
+                throw new IllegalArgumentException(String.format(
+                        "Prefix length %d is greater than max prefix length %d",
+                        prefix.getPrefixLength(), maxPrefixLength));
+            }
+
+            Node node = top;
+            Node match = null;
+
+            while (node != null
+                    && node.prefix.getPrefixLength() <= prefix.getPrefixLength()
+                    && checkKeyMatch(node.prefix.getAddress(),
+                            node.prefix.getPrefixLength(),
+                            prefix.getAddress(),
+                            prefix.getPrefixLength())) {
+                if (node.prefix.getPrefixLength() == prefix.getPrefixLength()) {
+                    /*
+                     * Prefix is already in tree. This may be an aggregate node, in which case
+                     * we are inserting a new prefix, or it could be an actual node, in which
+                     * case we are inserting a new nexthop for the prefix and should return
+                     * the old nexthop.
+                     */
+                    V oldValue = node.value;
+                    node.value = value;
+                    return oldValue;
+                }
+
+                match = node;
+
+                if (checkBit(prefix.getAddress(), node.prefix.getPrefixLength())) {
+                    node = node.right;
+                } else {
+                    node = node.left;
+                }
+            }
+
+            Node add = null;
+
+            if (node == null) {
+                //add = new Node(p, r);
+                add = new Node(prefix);
+                add.value = value;
+
+                if (match == null) {
+                    top = add;
+                } else {
+                    linkNodes(match, add);
+                }
+            } else {
+                add = findCommonNode(node, prefix.getAddress(), prefix.getPrefixLength());
+
+                if (match == null) {
+                    top = add;
+                } else {
+                    linkNodes(match, add);
+                }
+                linkNodes(add, node);
+
+                if (add.prefix.getPrefixLength() == prefix.getPrefixLength()) {
+                    add.value = value;
+                } else {
+                    match = add;
+
+                    //add = new Node(p, r);
+                    add = new Node(prefix);
+                    add.value = value;
+                    linkNodes(match, add);
+                }
+            }
+
+            //If we added a new Node, there was no previous mapping
+            return null;
+            //return addReference(add);
+        }
+    }
+
+    /*exact match*/
+    @Override
+    public V lookup(Prefix prefix) {
+        synchronized (this) {
+            if (prefix.getPrefixLength() > maxPrefixLength) {
+                return null;
+            }
+
+            /*
+            Node node = top;
+
+            while (node != null
+                    && node.prefix.getPrefixLength() <= p.getPrefixLength()
+                    && key_match(node.prefix.getAddress(), node.prefix.getPrefixLength(), p.getAddress(), p.getPrefixLength()) == true) {
+                if (node.prefix.getPrefixLength() == p.getPrefixLength()) {
+                    //return addReference(node);
+                    return node.rib;
+                }
+
+                if (bit_check(p.getAddress(), node.prefix.getPrefixLength()) == true) {
+                    node = node.right;
+                } else {
+                    node = node.left;
+                }
+            }
+            */
+
+            Node node = findNode(prefix);
+
+            return node == null ? null : node.value;
+        }
+    }
+
+    // closest containing prefix
+    @Override
+    public V match(Prefix prefix) {
+        //TODO
+        synchronized (this) {
+            if (prefix.getPrefixLength() > maxPrefixLength) {
+                return null;
+            }
+
+            Node closestNode = findClosestNode(prefix);
+
+            return closestNode == null ? null : closestNode.value;
+        }
+    }
+
+    @Override
+    public boolean remove(Prefix prefix, V value) {
+        synchronized (this) {
+            if (prefix == null || value == null) {
+                return false;
+            }
+
+            Node node = findNode(prefix);
+
+            if (node == null || node.isAggregate() || !node.value.equals(value)) {
+                //Given <prefix, nexthop> mapping is not in the tree
+                return false;
+            }
+
+            if (node.left != null && node.right != null) {
+                //Remove the RibEntry entry and leave this node as an aggregate node
+                //In the future, maybe we should re-evaluate what the aggregate prefix should be?
+                //It shouldn't necessarily stay the same.
+                //More complicated if the above prefix is also aggregate.
+                node.value = null;
+                return true;
+            }
+
+            Node child;
+            if (node.left != null) {
+                child = node.left;
+            } else {
+                child = node.right;
+            }
+
+            Node parent = node.parent;
+
+            if (child != null) {
+                child.parent = parent;
+            }
+
+            if (parent != null) {
+                if (parent.left == node) {
+                    parent.left = child;
+                } else {
+                    parent.right = child;
+                }
+            } else {
+                top = child;
+            }
+
+            /*
+             * TODO not sure what to do here. I think this is lazily deleting aggregate nodes,
+             * notice that it used to do nothing if it detected both children were not null earlier.
+             * But here, what we really should do is reevaluate the aggregate prefix of the parent
+             * node (if it is indeed an aggregate). Because at the moment, no aggregate node will ever
+             * be removed. BUT, I don't actually think this presents a correctness problem, at
+             * least from an external point of view.
+             */
+            //if (parent != null && parent.refCount == 0) {
+            //node_remove(parent);
+            //}
+
+            return true;
+        }
+    }
+
+    @Override
+    public Iterator<Entry<V>> iterator() {
+        return new PatriciaTreeIterator(top);
+    }
+
+    private Node findNode(Prefix prefix) {
+        Node node = top;
+
+        while (node != null
+                && node.prefix.getPrefixLength() <= prefix.getPrefixLength()
+                && checkKeyMatch(node.prefix.getAddress(),
+                        node.prefix.getPrefixLength(),
+                        prefix.getAddress(), prefix.getPrefixLength())) {
+            if (node.prefix.getPrefixLength() == prefix.getPrefixLength()) {
+                //return addReference(node);
+                return node;
+            }
+
+            if (checkBit(prefix.getAddress(), node.prefix.getPrefixLength())) {
+                node = node.right;
+            } else {
+                node = node.left;
+            }
+        }
+
+        return null;
+    }
+
+    private Node findClosestNode(Prefix prefix) {
+        Node node = top;
+        Node match = null;
+
+        while (node != null
+                && node.prefix.getPrefixLength() <= prefix.getPrefixLength()
+                && checkKeyMatch(node.prefix.getAddress(),
+                        node.prefix.getPrefixLength(),
+                        prefix.getAddress(), prefix.getPrefixLength())) {
+            if (!node.isAggregate()) {
+                match = node;
+            }
+
+            if (checkBit(prefix.getAddress(), node.prefix.getPrefixLength())) {
+                node = node.right;
+            } else {
+                node = node.left;
+            }
+        }
+
+        return match;
+    }
+
+    /*
+     * Receives a 1-based bit index
+     * Returns a 1-based byte index
+     * eg. (0 => 1), 1 => 1, 8 => 1, 9 => 2, 17 => 3
+     */
+    private int getByteContainingBit(int bitNumber) {
+        return Math.max((bitNumber + 7) / 8, 1);
+    }
+
+    private boolean checkKeyMatch(byte[] key1, int key1Length, byte[] key2, int key2Length) {
+        //int offset;
+        //int shift;
+
+        if (key1Length > key2Length) {
+            return false;
+        }
+
+        int offset = (Math.min(key1Length, key2Length)) / 8;
+        int shift = (Math.min(key1Length, key2Length)) % 8;
+
+        if (shift != 0) {
+            if ((maskBits[shift] & (key1[offset] ^ key2[offset])) != 0) {
+                return false;
+            }
+        }
+
+        while (offset != 0) {
+            offset--;
+            if (key1[offset] != key2[offset]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean checkBit(byte[] key, int keyBits) {
+        int offset = keyBits / 8;
+        int shift = 7 - (keyBits % 8);
+        int bit = key[offset] & 0xff;
+
+        bit >>= shift;
+
+        return ((bit & 1) == 1);
+    }
+
+    private void linkNodes(Node node, Node add) {
+        boolean bit = checkBit(add.prefix.getAddress(), node.prefix.getPrefixLength());
+
+        if (bit) {
+            node.right = add;
+        } else {
+            node.left = add;
+        }
+        add.parent = node;
+    }
+
+    private Node findCommonNode(Node node, byte[] key, int keyBits) {
+        int i;
+        int limit = Math.min(node.prefix.getPrefixLength(), keyBits) / 8;
+
+        for (i = 0; i < limit; i++) {
+            if (node.prefix.getAddress()[i] != key[i]) {
+                break;
+            }
+        }
+
+        int commonLen = i * 8;
+        int boundary = 0;
+
+        if (commonLen != keyBits) {
+            byte diff = (byte) (node.prefix.getAddress()[i] ^ key[i]);
+            byte mask = (byte) 0x80;
+            int shiftMask = 0;
+
+            while (commonLen < keyBits && ((mask & diff) == 0)) {
+                boundary = 1;
+
+                shiftMask = (mask & 0xff);
+                shiftMask >>= 1;
+                mask = (byte) shiftMask;
+
+                commonLen++;
+            }
+        }
+
+        //Creating a new Prefix with a prefix length of common_len
+        //Bits are copied from node's up until the common_len'th bit
+        //RibEntry is null, because this is an aggregate prefix - it's not
+        //actually been added to the tree.
+
+        byte[] newPrefix = new byte[getByteContainingBit(maxPrefixLength)];
+
+        int j;
+        for (j = 0; j < i; j++) {
+            newPrefix[j] = node.prefix.getAddress()[j];
+        }
+
+        if (boundary != 0) {
+            newPrefix[j] =
+                    (byte) (node.prefix.getAddress()[j] & maskBits[commonLen % 8]);
+        }
+
+        //return new Node(new Prefix(newPrefix, common_len), null);
+        return new Node(new Prefix(newPrefix, commonLen));
+        //return add;
+    }
+
+    private class Node {
+        public Node parent;
+        public Node left;
+        public Node right;
+
+        public final Prefix prefix;
+        public V value;
+
+        //public Node(Prefix p, RibEntry r) {
+        //      this.prefix = p;
+        //      this.rib = r;
+        //}
+        public Node(Prefix p) {
+            this.prefix = p;
+        }
+
+        public boolean isAggregate() {
+            return value == null;
+        }
+
+        public Entry<V> getEntry() {
+            return new PatriciaTreeEntry(prefix, value);
+        }
+    }
+
+    private class PatriciaTreeEntry implements Entry<V> {
+        private final Prefix prefix;
+        private final V value;
+
+        public PatriciaTreeEntry(Prefix prefix, V value) {
+            this.prefix = prefix;
+            this.value = value;
+        }
+
+        @Override
+        public Prefix getPrefix() {
+            return prefix;
+        }
+
+        @Override
+        public V getValue() {
+            return value;
+        }
+    }
+
+    private class PatriciaTreeIterator implements Iterator<Entry<V>> {
+        private Node current;
+        private boolean started; // initialized to false
+
+        public PatriciaTreeIterator(Node start) {
+            current = start;
+
+            //If the start is an aggregate node fast forward to find the next valid node
+            if (current != null && current.isAggregate()) {
+                current = findNext(current);
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (current == null) {
+                return false;
+            }
+
+            if (!started) {
+                return true;
+            }
+
+            return findNext(current) != null;
+        }
+
+        @Override
+        public Entry<V> next() {
+            if (current == null) {
+                throw new NoSuchElementException();
+            }
+
+            if (!started) {
+                started = true;
+                return current.getEntry();
+            }
+
+            current = findNext(current);
+            if (current == null) {
+                throw new NoSuchElementException();
+            }
+
+            return current.getEntry();
+        }
+
+        @Override
+        public void remove() {
+            // TODO This could be implemented, if it were needed
+            throw new NoSuchElementException();
+        }
+
+        private Node findNext(Node node) {
+            Node next = null;
+
+            if (node.left != null) {
+                next = node.left;
+                //addReference(next);
+                //delReference(node);
+                //return next;
+            } else if (node.right != null) {
+                next = node.right;
+                //addReference(next);
+                //delReference(node);
+                //return next;
+            } else {
+                //Node start = node;
+                while (node.parent != null) {
+                    if (node.parent.left == node && node.parent.right != null) {
+                        next = node.parent.right;
+                        //addReference(next);
+                        //delReference(start);
+                        //return next;
+                        break;
+                    }
+                    node = node.parent;
+                }
+            }
+
+            if (next == null) {
+                return null;
+            }
+
+            //If the node doesn't have a value, it's not an actual node, it's an artifically
+            //inserted aggregate node. We don't want to return these to the user.
+            if (next.isAggregate()) {
+                return findNext(next);
+            }
+
+            return next;
+        }
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/Prefix.java b/src/main/java/net/onrc/onos/apps/sdnip/Prefix.java
new file mode 100644
index 0000000..91510b1
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/Prefix.java
@@ -0,0 +1,187 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+import com.google.common.net.InetAddresses;
+
+/**
+ * Represents an IP prefix.
+ * <p/>
+ * It is made up of an IP address and a number of significant bits in the
+ * prefix (i.e. the size of the network part of the address).
+ * E.g. {@code 192.168.0.0/16}.
+ * <p/>
+ * Currently only IPv4 is supported, so a prefix length can be up to 32 bits.
+ */
+public class Prefix {
+    /**
+     * The length of addresses this class can represent prefixes of, in bytes.
+     */
+    public static final int ADDRESS_LENGTH = 4;
+
+    private final int prefixLength;
+    private final byte[] address;
+
+    // For verifying the arguments and pretty printing
+    private final InetAddress inetAddress;
+
+    /**
+     * Class constructor, taking an byte array representing and IP address and
+     * a prefix length.
+     * <p/>
+     * The valid values for addr and prefixLength are bounded by
+     * {@link #ADDRESS_LENGTH}.
+     * <p/>
+     * A valid addr array satisfies
+     * {@code addr.length == }{@value #ADDRESS_LENGTH}.
+     * <p/>
+     * A valid prefixLength satisfies
+     * {@code (prefixLength > 0 && prefixLength <=} {@link Byte#SIZE}
+     * {@code * }{@value #ADDRESS_LENGTH}{@code )}.
+     *
+     * @param addr a byte array representing the address
+     * @param prefixLength length of the prefix of the specified address
+     */
+    public Prefix(byte[] addr, int prefixLength) {
+        if (addr == null || addr.length != ADDRESS_LENGTH ||
+                prefixLength < 0 || prefixLength > ADDRESS_LENGTH * Byte.SIZE) {
+            throw new IllegalArgumentException();
+        }
+
+        address = canonicalizeAddress(addr, prefixLength);
+        this.prefixLength = prefixLength;
+
+        try {
+            inetAddress = InetAddress.getByAddress(address);
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("Couldn't parse IP address", e);
+        }
+    }
+
+    /**
+     * Class constructor, taking an address in String format and a prefix
+     * length. The address must be in dot-notation form (e.g. {@code 0.0.0.0}).
+     *
+     * @param strAddress a String representing the address
+     * @param prefixLength length of the prefix of the specified address
+     */
+    public Prefix(String strAddress, int prefixLength) {
+        byte[] addr = null;
+        addr = InetAddresses.forString(strAddress).getAddress();
+
+        if (addr == null || addr.length != ADDRESS_LENGTH ||
+                prefixLength < 0 || prefixLength > ADDRESS_LENGTH * Byte.SIZE) {
+            throw new IllegalArgumentException();
+        }
+
+        address = canonicalizeAddress(addr, prefixLength);
+        this.prefixLength = prefixLength;
+
+        try {
+            inetAddress = InetAddress.getByAddress(address);
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("Couldn't parse IP address", e);
+        }
+    }
+
+    private byte[] canonicalizeAddress(byte[] addressValue,
+                                       int prefixLengthValue) {
+        byte[] result = new byte[addressValue.length];
+
+        if (prefixLengthValue == 0) {
+            for (int i = 0; i < ADDRESS_LENGTH; i++) {
+                result[i] = 0;
+            }
+
+            return result;
+        }
+
+        result = Arrays.copyOf(addressValue, addressValue.length);
+
+        //Set all bytes after the end of the prefix to 0
+        int lastByteIndex = (prefixLengthValue - 1) / Byte.SIZE;
+        for (int i = lastByteIndex; i < ADDRESS_LENGTH; i++) {
+            result[i] = 0;
+        }
+
+        byte lastByte = addressValue[lastByteIndex];
+        byte mask = 0;
+        byte msb = (byte) 0x80;
+        int lastBit = (prefixLengthValue - 1) % Byte.SIZE;
+        for (int i = 0; i < Byte.SIZE; i++) {
+            if (i <= lastBit) {
+                mask |= (msb >> i);
+            }
+        }
+
+        result[lastByteIndex] = (byte) (lastByte & mask);
+
+        return result;
+    }
+
+    /**
+     * Gets the length of the prefix of the address.
+     *
+     * @return the prefix length
+     */
+    public int getPrefixLength() {
+        return prefixLength;
+    }
+
+    /**
+     * Gets the address.
+     *
+     * @return the address as a byte array
+     */
+    public byte[] getAddress() {
+        return Arrays.copyOf(address, address.length);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof Prefix)) {
+            return false;
+        }
+
+        Prefix otherPrefix = (Prefix) other;
+
+        return (Arrays.equals(address, otherPrefix.address)) &&
+                (prefixLength == otherPrefix.prefixLength);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 17;
+        hash = 31 * hash + prefixLength;
+        hash = 31 * hash + Arrays.hashCode(address);
+        return hash;
+    }
+
+    @Override
+    public String toString() {
+        return inetAddress.getHostAddress() + "/" + prefixLength;
+    }
+
+    /**
+     * Print the prefix to a String showing the bits of the address.
+     *
+     * @return the bit-string of the prefix
+     */
+    public String printAsBits() {
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < address.length; i++) {
+            byte b = address[i];
+            for (int j = 0; j < Byte.SIZE; j++) {
+                byte mask = (byte) (0x80 >>> j);
+                result.append(((b & mask) == 0) ? "0" : "1");
+                if (i * Byte.SIZE + j == prefixLength - 1) {
+                    return result.toString();
+                }
+            }
+            result.append(' ');
+        }
+        return result.substring(0, result.length() - 1);
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/Ptree.java b/src/main/java/net/onrc/onos/apps/sdnip/Ptree.java
new file mode 100644
index 0000000..c99ea09
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/Ptree.java
@@ -0,0 +1,318 @@
+package net.onrc.onos.apps.sdnip;
+
+/*
+ * TODO This Ptree needs to be refactored if we're going to use it permenantly.
+ *
+ * The biggest problem is it leaks PTreeNode references - these need to stay within
+ * the Ptree as they contain data fundamental to the structure of the tree.
+ * You should put RIB entries in and get RIB entries out.
+ * Also we need to get rid of the referencing scheme to determine when to delete nodes.
+ * Deletes should be explicit, and there's no need to keep track of references if
+ * we don't leak them out the the Ptree.
+ */
+public class Ptree {
+    private int maxKeyBits;
+    private int maxKeyOctets;
+    //private int refCount;
+    private PtreeNode top;
+    private byte[] maskBits = {(byte) 0x00, (byte) 0x80, (byte) 0xc0, (byte) 0xe0, (byte) 0xf0, (byte) 0xf8, (byte) 0xfc, (byte) 0xfe, (byte) 0xff};
+
+    public Ptree(int maxKeyBits) {
+        this.maxKeyBits = maxKeyBits;
+        maxKeyOctets = bitToOctet(maxKeyBits);
+        //refCount = 0;
+    }
+
+    public synchronized PtreeNode acquire(byte[] key) {
+        return acquire(key, maxKeyBits);
+    }
+
+    public synchronized PtreeNode acquire(byte[] key, int keyBits) {
+        if (keyBits > maxKeyBits) {
+            return null;
+        }
+
+        PtreeNode node = top;
+        PtreeNode match = null;
+
+        while (node != null
+                && node.keyBits <= keyBits
+                && keyMatch(node.key, node.keyBits, key, keyBits)) {
+            if (node.keyBits == keyBits) {
+                return addReference(node);
+            }
+
+            match = node;
+
+            if (bitCheck(key, node.keyBits)) {
+                node = node.right;
+            } else {
+                node = node.left;
+            }
+        }
+
+        PtreeNode add = null;
+
+        if (node == null) {
+            add = new PtreeNode(key, keyBits, maxKeyOctets);
+
+            if (match != null) {
+                nodeLink(match, add);
+            } else {
+                top = add;
+            }
+        } else {
+            add = nodeCommon(node, key, keyBits);
+
+            if (match != null) {
+                nodeLink(match, add);
+            } else {
+                top = add;
+            }
+            nodeLink(add, node);
+
+            if (add.keyBits != keyBits) {
+                match = add;
+
+                add = new PtreeNode(key, keyBits, maxKeyOctets);
+                nodeLink(match, add);
+            }
+        }
+
+        return addReference(add);
+    }
+
+    public synchronized PtreeNode lookup(byte[] key, int keyBits) {
+        if (keyBits > maxKeyBits) {
+            return null;
+        }
+
+        PtreeNode node = top;
+
+        while (node != null
+                && node.keyBits <= keyBits
+                && keyMatch(node.key, node.keyBits, key, keyBits)) {
+            if (node.keyBits == keyBits) {
+                return addReference(node);
+            }
+
+            if (bitCheck(key, node.keyBits)) {
+                node = node.right;
+            } else {
+                node = node.left;
+            }
+        }
+        return null;
+    }
+
+    public synchronized PtreeNode match(byte[] key, int keyBits) {
+        if (keyBits > maxKeyBits) {
+            return null;
+        }
+        PtreeNode node = top;
+        PtreeNode matched = null;
+
+        if (node != null) {
+
+            while (node != null
+                    && node.keyBits <= keyBits
+                    && keyMatch(node.key, node.keyBits, key, keyBits)) {
+                matched = node;
+
+                if (bitCheck(key, node.keyBits)) {
+                    node = node.right;
+                } else {
+                    node = node.left;
+                }
+            }
+        }
+
+        if (matched != null) {
+            return addReference(matched);
+        }
+
+        return null;
+    }
+
+    public synchronized PtreeNode begin() {
+        if (top == null) {
+            return null;
+        }
+        return addReference(top);
+    }
+
+    public synchronized PtreeNode next(PtreeNode node) {
+        PtreeNode next;
+
+        if (node.left != null) {
+            next = node.left;
+            addReference(next);
+            delReference(node);
+            return next;
+        }
+        if (node.right != null) {
+            next = node.right;
+            addReference(next);
+            delReference(node);
+            return next;
+        }
+
+        PtreeNode start = node;
+        while (node.parent != null) {
+            if (node.parent.left == node && node.parent.right != null) {
+                next = node.parent.right;
+                addReference(next);
+                delReference(start);
+                return next;
+            }
+            node = node.parent;
+        }
+
+        delReference(start);
+
+        return null;
+    }
+
+    public static int bitToOctet(int keyBits) {
+        return Math.max((keyBits + 7) / 8, 1);
+    }
+
+    private PtreeNode addReference(PtreeNode node) {
+        node.refCount++;
+        return node;
+    }
+
+    public synchronized void delReference(PtreeNode node) {
+        if (node.refCount > 0) {
+            node.refCount--;
+        }
+        if (node.refCount == 0) {
+            nodeRemove(node);
+        }
+    }
+
+    private boolean keyMatch(byte[] key1, int key1Len, byte[] key2, int key2Len) {
+        int offset;
+        int shift;
+
+        if (key1Len > key2Len) {
+            return false;
+        }
+
+        offset = (Math.min(key1Len, key2Len)) / 8;
+        shift = (Math.min(key1Len, key2Len)) % 8;
+
+        if (shift != 0) {
+            if ((maskBits[shift] & (key1[offset] ^ key2[offset])) != 0) {
+                return false;
+            }
+        }
+
+        while (offset != 0) {
+            offset--;
+            if (key1[offset] != key2[offset]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean bitCheck(byte[] key, int keyBits) {
+        int offset = keyBits / 8;
+        int shift = 7 - (keyBits % 8);
+        int bit = key[offset] & 0xff;
+
+        bit >>= shift;
+
+        return ((bit & 1) == 1);
+    }
+
+    private void nodeLink(PtreeNode node, PtreeNode add) {
+        boolean bit = bitCheck(add.key, node.keyBits);
+
+        if (bit) {
+            node.right = add;
+        } else {
+            node.left = add;
+        }
+        add.parent = node;
+    }
+
+    private PtreeNode nodeCommon(PtreeNode node, byte[] key, int keyBits) {
+        int i;
+        int limit = Math.min(node.keyBits, keyBits) / 8;
+
+        for (i = 0; i < limit; i++) {
+            if (node.key[i] != key[i]) {
+                break;
+            }
+        }
+
+        int commonLen = i * 8;
+        int boundary = 0;
+
+        if (commonLen != keyBits) {
+            byte diff = (byte) (node.key[i] ^ key[i]);
+            byte mask = (byte) 0x80;
+            int shiftMask = 0;
+
+            while (commonLen < keyBits && ((mask & diff) == 0)) {
+                boundary = 1;
+
+                shiftMask = (mask & 0xff);
+                shiftMask >>= 1;
+                mask = (byte) shiftMask;
+
+                commonLen++;
+            }
+        }
+
+        PtreeNode add = new PtreeNode(null, commonLen, maxKeyOctets);
+
+        int j;
+        for (j = 0; j < i; j++) {
+            add.key[j] = node.key[j];
+        }
+
+        if (boundary != 0) {
+            add.key[j] = (byte) (node.key[j] & maskBits[add.keyBits % 8]);
+        }
+
+        return add;
+    }
+
+    private void nodeRemove(PtreeNode node) {
+        PtreeNode child;
+        PtreeNode parent;
+
+        if (node.left != null && node.right != null) {
+            return;
+        }
+
+        if (node.left != null) {
+            child = node.left;
+        } else {
+            child = node.right;
+        }
+
+        parent = node.parent;
+
+        if (child != null) {
+            child.parent = parent;
+        }
+
+        if (parent != null) {
+            if (parent.left == node) {
+                parent.left = child;
+            } else {
+                parent.right = child;
+            }
+        } else {
+            top = child;
+        }
+
+        if (parent != null && parent.refCount == 0) {
+            nodeRemove(parent);
+        }
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/PtreeNode.java b/src/main/java/net/onrc/onos/apps/sdnip/PtreeNode.java
new file mode 100644
index 0000000..b54546c
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/PtreeNode.java
@@ -0,0 +1,44 @@
+package net.onrc.onos.apps.sdnip;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PtreeNode {
+    public PtreeNode parent;
+    public PtreeNode left;
+    public PtreeNode right;
+
+    public byte[] key;
+    public int keyBits;
+
+    public int refCount;
+
+    // public RibEntry rib;
+    private static final Logger log = LoggerFactory.getLogger(PtreeNode.class);
+
+    PtreeNode(byte[] key, int keyBits, int maxKeyOctet) {
+        parent = null;
+        left = null;
+        right = null;
+        refCount = 0;
+        // rib = null;
+        this.key = new byte[maxKeyOctet];
+        this.keyBits = keyBits;
+        log.debug("inside Ptreenode constructor key {} bits {}", key, keyBits);
+
+        int octet = Ptree.bitToOctet(keyBits);
+        for (int i = 0; i < maxKeyOctet; i++) {
+            if (i < octet) {
+                if (key != null) {
+                    log.debug(octet + ": filling key[{}] {}", i, key[i]);
+                    this.key[i] = key[i];
+                } else {
+                    log.debug("no filling, null key", i);
+                }
+            } else {
+                log.debug("filling key {} as 0", i);
+                this.key[i] = 0;
+            }
+        }
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/PushedFlowMod.java b/src/main/java/net/onrc/onos/apps/sdnip/PushedFlowMod.java
new file mode 100644
index 0000000..38ac37f
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/PushedFlowMod.java
@@ -0,0 +1,46 @@
+package net.onrc.onos.apps.sdnip;
+
+import org.openflow.protocol.OFFlowMod;
+
+// TODO This functionality should be handled by ONOS's flow layer in future.
+/**
+ * Collects together the DPID and OFFlowMod of a pushed flow mod. This
+ * information is used if the flow mod has to be deleted in the future.
+ */
+public class PushedFlowMod {
+    private final long dpid;
+    private OFFlowMod flowMod;
+
+    /**
+     * Class constructor, taking a DPID and a flow mod.
+     *
+     * @param dpid the DPID of the switch the flow mod was pushed to
+     * @param flowMod the OFFlowMod that was pushed to the switch
+     */
+    public PushedFlowMod(long dpid, OFFlowMod flowMod) {
+        this.dpid = dpid;
+        try {
+            this.flowMod = flowMod.clone();
+        } catch (CloneNotSupportedException e) {
+            this.flowMod = flowMod;
+        }
+    }
+
+    /**
+     * Gets the DPID of the switch the flow mod was pushed to.
+     *
+     * @return the DPID of the switch
+     */
+    public long getDpid() {
+        return dpid;
+    }
+
+    /**
+     * Gets the OFFlowMod that was pushed to the switch.
+     *
+     * @return the OFFlowMod object
+     */
+    public OFFlowMod getFlowMod() {
+        return flowMod;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/RestClient.java b/src/main/java/net/onrc/onos/apps/sdnip/RestClient.java
new file mode 100644
index 0000000..fe349d5
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/RestClient.java
@@ -0,0 +1,131 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.httpclient.ConnectTimeoutException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A simple HTTP client. It is used to make REST calls to the BGPd process.
+ */
+public final class RestClient {
+    private static final Logger log = LoggerFactory.getLogger(RestClient.class);
+
+    /**
+     * Default constructor.
+     */
+    private RestClient() {
+        // Private constructor to prevent instantiation
+    }
+
+    /**
+     * Issues a HTTP GET request to the specified URL.
+     *
+     * @param strUrl the URL to make the request to
+     * @return a String containing any data returned by the server
+     */
+    public static String get(String strUrl) {
+        StringBuilder response = new StringBuilder();
+
+        try {
+            URL url = new URL(strUrl);
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setConnectTimeout(2 * 1000); // 2 seconds
+            conn.setRequestMethod("GET");
+            conn.setRequestProperty("Accept", "application/json");
+
+            if (conn.getResponseCode() != 200) {
+                // XXX bad. RestClient API needs to be redesigned
+                throw new IOException("Failed : HTTP error code : "
+                        + conn.getResponseCode());
+            }
+
+            if (!conn.getContentType().equals("application/json")) {
+                log.warn("The content received from {} is not json", strUrl);
+            }
+
+            BufferedReader br = new BufferedReader(new InputStreamReader(
+                            conn.getInputStream(), StandardCharsets.UTF_8));
+            String line;
+            while ((line = br.readLine()) != null) {
+                response.append(line);
+            }
+
+            br.close();
+            conn.disconnect();
+
+        } catch (MalformedURLException e) {
+            log.error("Malformed URL for GET request", e);
+        } catch (ConnectTimeoutException e) {
+            log.warn("Couldn't connect to the remote REST server", e);
+        } catch (IOException e) {
+            log.warn("Couldn't connect to the remote REST server", e);
+        }
+
+        return response.toString();
+    }
+
+    /**
+     * Issues a HTTP POST request to the specified URL.
+     *
+     * @param strUrl the URL to make the request to
+     */
+    public static void post(String strUrl) {
+
+        try {
+            URL url = new URL(strUrl);
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setDoOutput(true);
+            conn.setRequestMethod("POST");
+            conn.setRequestProperty("Content-Type", "application/json");
+
+            if (conn.getResponseCode() != 200) {
+                // XXX bad. RestClient API needs to be redesigned
+                throw new IOException("Failed : HTTP error code : "
+                        + conn.getResponseCode());
+            }
+
+            conn.disconnect();
+
+        } catch (MalformedURLException e) {
+            log.error("Malformed URL for GET request", e);
+        } catch (IOException e) {
+            log.warn("Couldn't connect to the remote REST server", e);
+        }
+    }
+
+    /**
+     * Issues a HTTP DELETE request to the specified URL.
+     *
+     * @param strUrl the URL to make the request to
+     */
+    public static void delete(String strUrl) {
+
+        try {
+            URL url = new URL(strUrl);
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("DELETE");
+            conn.setRequestProperty("Accept", "application/json");
+
+            if (conn.getResponseCode() != 200) {
+                // XXX bad. RestClient API needs to be redesigned
+                throw new IOException("Failed : HTTP error code : "
+                        + conn.getResponseCode());
+            }
+
+            conn.disconnect();
+
+        } catch (MalformedURLException e) {
+            log.error("Malformed URL for GET request", e);
+        } catch (IOException e) {
+            log.warn("Couldn't connect to the remote REST server", e);
+        }
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/RibEntry.java b/src/main/java/net/onrc/onos/apps/sdnip/RibEntry.java
new file mode 100644
index 0000000..47eca6b
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/RibEntry.java
@@ -0,0 +1,132 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.net.InetAddress;
+
+import com.google.common.net.InetAddresses;
+
+/**
+ * Represents an entry in the Routing Information Base (RIB) of a router.
+ * <p/>
+ * A route update from the BGP daemon contains a prefix and a RibEntry
+ * containing the next hop for the route. The RibEntry also contains
+ * information related to the synchronization mechanism between BGPd and
+ * SDN-IP, such as sequence numbers.
+ */
+public class RibEntry {
+    private final InetAddress routerId;
+    private final InetAddress nextHop;
+
+    /*
+     * Store the sequence number information provided on the update here for
+     * now. I think this *should* really be in the RibUpdate, and we should
+     * store RibUpdates in the Ptree. But, that's a bigger change to change
+     * what the Ptree stores.
+     */
+    private final long sysUpTime;
+    private final long sequenceNum;
+
+    /*
+     * Marker for RibEntries where we don't have sequence number info.
+     * The user of this class should make sure they don't check this data
+     * if they don't provide it.
+     */
+    private static final long NULL_TIME = -1;
+
+    /**
+     * Class constructor, taking the router ID and next hop IP address as
+     * {@link InetAddress} objects.
+     *
+     * @param routerId the router ID which identifies the route table in BGPd
+     * that this update came from
+     * @param nextHop next hop IP address for this route entry
+     */
+    public RibEntry(InetAddress routerId, InetAddress nextHop) {
+        this.routerId = routerId;
+        this.nextHop = nextHop;
+        sequenceNum = NULL_TIME;
+        sysUpTime = NULL_TIME;
+    }
+
+    /**
+     * Class constructor, taking the router ID and next hop IP address as
+     * Strings. The addresses must be in dot-notation form
+     * (e.g. {@code 0.0.0.0}).
+     *
+     * @param routerId the router ID which identifies the route table in BGPd
+     * that this update came from
+     * @param nextHop next hop IP address for this route entry
+     */
+    public RibEntry(String routerId, String nextHop) {
+        this.routerId = InetAddresses.forString(routerId);
+        this.nextHop = InetAddresses.forString(nextHop);
+        sequenceNum = NULL_TIME;
+        sysUpTime = NULL_TIME;
+    }
+
+    /**
+     * Class constructor, taking the router ID and next hop IP address as
+     * Strings, as well as the sequence numbers of the updates. Sequence
+     * numbers are used to establish ordering of updates from BGPd. The
+     * addresses must be in dot-notation form (e.g. {@code 0.0.0.0}).
+     *
+     * @param routerId the router ID which identifies the route table in BGPd
+     * that this update came from
+     * @param nextHop next hop IP address for this route entry
+     * @param sysUpTime the sysuptime parameter on the update from BGPd
+     * @param sequenceNum the sequencenum parameter on the update from BGPd
+     */
+    public RibEntry(String routerId, String nextHop, long sysUpTime,
+            long sequenceNum) {
+        this.routerId = InetAddresses.forString(routerId);
+        this.nextHop = InetAddresses.forString(nextHop);
+        this.sequenceNum = sequenceNum;
+        this.sysUpTime = sysUpTime;
+    }
+
+    /**
+     * Gets the next hop IP address of this route entry.
+     *
+     * @return the next hop IP address
+     */
+    public InetAddress getNextHop() {
+        return nextHop;
+    }
+
+    /**
+     * Gets the sysuptime parameter sent with the update from BGPd.
+     *
+     * @return the sysuptime parameter
+     */
+    public long getSysUpTime() {
+        return sysUpTime;
+    }
+
+    /**
+     * Gets the sequencenum parameter sent with the update from BGPd.
+     *
+     * @return the sequencenum parameter
+     */
+    public long getSequenceNum() {
+        return sequenceNum;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof RibEntry)) {
+            return false;
+        }
+
+        RibEntry otherRibEntry = (RibEntry) other;
+
+        return this.routerId.equals(otherRibEntry.routerId)
+                && this.nextHop.equals(otherRibEntry.nextHop);
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 17;
+        hash = 31 * hash + routerId.hashCode();
+        hash = 31 * hash + nextHop.hashCode();
+        return hash;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/RibUpdate.java b/src/main/java/net/onrc/onos/apps/sdnip/RibUpdate.java
new file mode 100644
index 0000000..0476979
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/RibUpdate.java
@@ -0,0 +1,73 @@
+package net.onrc.onos.apps.sdnip;
+
+/**
+ * Represents a route update received from BGPd. An update has an operation
+ * describing whether the update is adding a route or revoking a route. It also
+ * contains the route prefix, and {@link RibEntry} containing next hop and
+ * sequence number information for the update.
+ */
+public class RibUpdate {
+    private final Operation operation;
+    private final Prefix prefix;
+    private final RibEntry ribEntry;
+
+    /**
+     * Updates can either add new routes or revoke old routes. The
+     * {@link Operation} enum descibes which action is being taken.
+     */
+    public enum Operation {
+        /**
+         * Represents a route update. ONOS should update its route information
+         * for this prefix to the new information provided in this
+         * {@link RibUpdate}. This means either add a new prefix, or update
+         * the information for an existing prefix.
+         */
+        UPDATE,
+        /**
+         * Represents a route delete. ONOS should remove this prefix and route
+         * information from its route table.
+         */
+        DELETE
+    }
+
+    /**
+     * Class constructor, taking the operation of the update, the route prefix
+     * and the {@link RibEntry} describing the update.
+     *
+     * @param operation the operation of the update
+     * @param prefix the route prefix
+     * @param ribEntry the update entry
+     */
+    public RibUpdate(Operation operation, Prefix prefix, RibEntry ribEntry) {
+        this.operation = operation;
+        this.prefix = prefix;
+        this.ribEntry = ribEntry;
+    }
+
+    /**
+     * Gets the operation of the update.
+     *
+     * @return the operation
+     */
+    public Operation getOperation() {
+        return operation;
+    }
+
+    /**
+     * Gets the route prefix of the update.
+     *
+     * @return the prefix
+     */
+    public Prefix getPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Gets the {@link RibEntry} of the update.
+     *
+     * @return the entry
+     */
+    public RibEntry getRibEntry() {
+        return ribEntry;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/SdnIp.java b/src/main/java/net/onrc/onos/apps/sdnip/SdnIp.java
new file mode 100644
index 0000000..4e4e6fa
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/SdnIp.java
@@ -0,0 +1,1406 @@
+package net.onrc.onos.apps.sdnip;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+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 java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+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.restserver.IRestApiService;
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.apps.proxyarp.IArpRequester;
+import net.onrc.onos.apps.proxyarp.IProxyArpService;
+import net.onrc.onos.apps.sdnip.RibUpdate.Operation;
+import net.onrc.onos.apps.sdnip.web.SdnIpWebRoutable;
+import net.onrc.onos.core.linkdiscovery.ILinkDiscovery.LDUpdate;
+import net.onrc.onos.core.linkdiscovery.ILinkDiscoveryService;
+import net.onrc.onos.core.main.config.IConfigInfoService;
+import net.onrc.onos.core.packet.Ethernet;
+import net.onrc.onos.core.packet.IPv4;
+import net.onrc.onos.core.util.CallerId;
+import net.onrc.onos.core.util.DataPath;
+import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.FlowEntryAction;
+import net.onrc.onos.core.util.FlowEntryActions;
+import net.onrc.onos.core.util.FlowEntryMatch;
+import net.onrc.onos.core.util.FlowId;
+import net.onrc.onos.core.util.FlowPath;
+import net.onrc.onos.core.util.FlowPathFlags;
+import net.onrc.onos.core.util.FlowPathType;
+import net.onrc.onos.core.util.FlowPathUserState;
+import net.onrc.onos.core.util.IPv4Net;
+import net.onrc.onos.core.util.Port;
+import net.onrc.onos.core.util.SwitchPort;
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+import net.sf.json.JSONSerializer;
+
+import org.apache.commons.configuration.ConfigurationRuntimeException;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+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.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.net.InetAddresses;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+public class SdnIp implements IFloodlightModule, ISdnIpService,
+        IArpRequester,
+        IOFSwitchListener, IConfigInfoService {
+
+    private static final Logger log = LoggerFactory.getLogger(SdnIp.class);
+
+    private IFloodlightProviderService floodlightProvider;
+    private ILinkDiscoveryService linkDiscoveryService;
+    private IRestApiService restApi;
+    private IProxyArpService proxyArp;
+
+    private IPatriciaTree<RibEntry> ptree;
+    private IPatriciaTree<Interface> interfacePtree;
+    private BlockingQueue<RibUpdate> ribUpdates;
+
+    private String bgpdRestIp;
+    private String routerId;
+    private static final String DEFAULT_CONFIG_FILENAME = "config.json";
+    private String currentConfigFilename = DEFAULT_CONFIG_FILENAME;
+
+    private static final short ARP_PRIORITY = 20;
+
+    // The fields below are unused after the move to FlowManager.
+    // Remove them if no longer needed.
+    /*
+    // We need to identify our flows somehow, in lieu of an OS-wide mechanism
+    // to hand out cookie IDs to prevent conflicts.
+    private static final long APP_COOKIE = 0xa0000000000000L;
+    // Cookie for flows that do L2 forwarding within SDN domain to egress routers
+    private static final long L2_FWD_COOKIE = APP_COOKIE + 1;
+    // Cookie for flows in ingress switches that rewrite the MAC address
+    private static final long MAC_RW_COOKIE = APP_COOKIE + 2;
+    // Cookie for flows that setup BGP paths
+    private static 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
+    private static final short SDNIP_PRIORITY = 10;
+    */
+
+    private static final short BGP_PORT = 179;
+
+    private static final int TOPO_DETECTION_WAIT = 2; // seconds
+
+    // Configuration stuff
+    private List<String> switches;
+    private Map<String, Interface> interfaces;
+    private Map<InetAddress, BgpPeer> bgpPeers;
+    private SwitchPort bgpdAttachmentPoint;
+    private MACAddress bgpdMacAddress;
+    private short vlan;
+
+    // True when all switches have connected
+    private volatile boolean switchesConnected = false;
+    // True when we have a full mesh of shortest paths between gateways
+    private volatile boolean topologyReady = false;
+
+    private List<LDUpdate> linkUpdates;
+    private SingletonTask topologyChangeDetectorTask;
+
+    private SetMultimap<InetAddress, RibUpdate> prefixesWaitingOnArp;
+
+    private Map<InetAddress, Path> pathsWaitingOnArp;
+
+    private ExecutorService bgpUpdatesExecutor;
+
+    private Map<InetAddress, Path> pushedPaths;
+    private Map<Prefix, Path> prefixToPath;
+    //  private Multimap<Prefix, PushedFlowMod> pushedFlows;
+    private Multimap<Prefix, FlowId> pushedFlowIds;
+
+    private FlowCache flowCache;
+
+    // TODO: Fix for the new Topology Network Graph
+    // private volatile Topology topology = null;
+
+    private class TopologyChangeDetector implements Runnable {
+        @Override
+        public void run() {
+            log.debug("Running topology change detection task");
+            // TODO: Fix the code below after topoLinkService was removed
+            /*
+            synchronized (linkUpdates) {
+
+                ITopoLinkService topoLinkService = new TopoLinkServiceImpl();
+
+                List<Link> activeLinks = topoLinkService.getActiveLinks();
+
+                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)){
+                        it.remove();
+                    }
+                }
+            }
+            */
+
+            if (!topologyReady) {
+                if (linkUpdates.isEmpty()) {
+                    // All updates have been seen in network map.
+                    // We can check if topology is ready
+                    log.debug("No known 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 - {} links missing", linkUpdates.size());
+                    topologyChangeDetectorTask.reschedule(TOPO_DETECTION_WAIT, TimeUnit.SECONDS);
+                }
+            }
+        }
+    }
+
+    private void readConfiguration(String configFilename) {
+        File gatewaysFile = new File(configFilename);
+        ObjectMapper mapper = new ObjectMapper();
+
+        try {
+            Configuration config = mapper.readValue(gatewaysFile, Configuration.class);
+
+            switches = config.getSwitches();
+            interfaces = new HashMap<String, Interface>();
+            for (Interface intf : config.getInterfaces()) {
+                interfaces.put(intf.getName(), intf);
+            }
+            bgpPeers = new HashMap<InetAddress, BgpPeer>();
+            for (BgpPeer peer : config.getPeers()) {
+                bgpPeers.put(peer.getIpAddress(), peer);
+            }
+
+            bgpdAttachmentPoint = new SwitchPort(
+                    new Dpid(config.getBgpdAttachmentDpid()),
+                    new Port(config.getBgpdAttachmentPort()));
+
+            bgpdMacAddress = config.getBgpdMacAddress();
+            vlan = config.getVlan();
+        } catch (JsonParseException e) {
+            log.error("Error in JSON file", e);
+            throw new ConfigurationRuntimeException("Error in JSON file", e);
+        } catch (JsonMappingException e) {
+            log.error("Error in JSON file", e);
+            throw new ConfigurationRuntimeException("Error in JSON file", e);
+        } catch (IOException e) {
+            log.error("Error reading JSON file", e);
+            throw new ConfigurationRuntimeException("Error in JSON file", e);
+        }
+
+        // Populate the interface Patricia Tree
+        for (Interface intf : interfaces.values()) {
+            Prefix prefix = new Prefix(intf.getIpAddress().getAddress(), intf.getPrefixLength());
+            interfacePtree.put(prefix, intf);
+        }
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l
+                = new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(ISdnIpService.class);
+        l.add(IConfigInfoService.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(ISdnIpService.class, this);
+        m.put(IConfigInfoService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+        Collection<Class<? extends IFloodlightService>> l
+                = new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IFloodlightProviderService.class);
+        l.add(IRestApiService.class);
+        return l;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+            throws FloodlightModuleException {
+
+        ptree = new PatriciaTree<RibEntry>(32);
+        interfacePtree = new PatriciaTree<Interface>(32);
+
+        ribUpdates = new LinkedBlockingQueue<RibUpdate>();
+
+        // Register floodlight provider and REST handler.
+        floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
+        linkDiscoveryService = context.getServiceImpl(ILinkDiscoveryService.class);
+        restApi = context.getServiceImpl(IRestApiService.class);
+        proxyArp = context.getServiceImpl(IProxyArpService.class);
+
+        linkUpdates = new ArrayList<LDUpdate>();
+        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
+        topologyChangeDetectorTask = new SingletonTask(executor, new TopologyChangeDetector());
+
+        pathsWaitingOnArp = new HashMap<InetAddress, Path>();
+        prefixesWaitingOnArp = Multimaps.synchronizedSetMultimap(
+                HashMultimap.<InetAddress, RibUpdate>create());
+
+        pushedPaths = new HashMap<InetAddress, Path>();
+        prefixToPath = new HashMap<Prefix, Path>();
+//              pushedFlows = HashMultimap.<Prefix, PushedFlowMod>create();
+        pushedFlowIds = HashMultimap.<Prefix, FlowId>create();
+
+        flowCache = new FlowCache(floodlightProvider);
+
+        bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
+                new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build());
+
+        // Read in config values
+        bgpdRestIp = context.getConfigParams(this).get("BgpdRestIp");
+        if (bgpdRestIp == null) {
+            log.error("BgpdRestIp property not found in config file");
+            throw new ConfigurationRuntimeException(
+                    "BgpdRestIp property not found in config file");
+        } else {
+            log.info("BgpdRestIp set to {}", bgpdRestIp);
+        }
+
+        routerId = context.getConfigParams(this).get("RouterId");
+        if (routerId == null) {
+            log.error("RouterId property not found in config file");
+            throw new ConfigurationRuntimeException(
+                    "RouterId property not found in config file");
+        } else {
+            log.info("RouterId set to {}", routerId);
+        }
+
+        String configFilenameParameter = context.getConfigParams(this).get("configfile");
+        if (configFilenameParameter != null) {
+            currentConfigFilename = configFilenameParameter;
+        }
+        log.debug("Config file set to {}", currentConfigFilename);
+
+        readConfiguration(currentConfigFilename);
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        restApi.addRestletRoutable(new SdnIpWebRoutable());
+        floodlightProvider.addOFSwitchListener(this);
+
+        // Retrieve the RIB from BGPd during startup
+        retrieveRib();
+    }
+
+    @Override
+    public IPatriciaTree<RibEntry> getPtree() {
+        return ptree;
+    }
+
+    @Override
+    public void clearPtree() {
+        ptree = new PatriciaTree<RibEntry>(32);
+    }
+
+    @Override
+    public String getBgpdRestIp() {
+        return bgpdRestIp;
+    }
+
+    @Override
+    public String getRouterId() {
+        return routerId;
+    }
+
+    private void retrieveRib() {
+        String url = "http://" + bgpdRestIp + "/wm/bgp/" + routerId;
+        String response = RestClient.get(url);
+
+        if ("".equals(response)) {
+            return;
+        }
+
+        response = response.replaceAll("\"", "'");
+        JSONObject jsonObj = (JSONObject) JSONSerializer.toJSON(response);
+        JSONArray ribArray = jsonObj.getJSONArray("rib");
+        String inboundRouterId = jsonObj.getString("router-id");
+
+        int size = ribArray.size();
+
+        log.info("Retrived RIB of {} entries from BGPd", size);
+
+        for (int j = 0; j < size; j++) {
+            JSONObject ribEntry = ribArray.getJSONObject(j);
+            String prefix = ribEntry.getString("prefix");
+            String nexthop = ribEntry.getString("nexthop");
+
+            // Insert each rib entry into the local rib
+            String[] substring = prefix.split("/");
+            String prefix1 = substring[0];
+            String mask1 = substring[1];
+
+            Prefix p;
+            try {
+                p = new Prefix(prefix1, Integer.valueOf(mask1));
+            } catch (NumberFormatException e) {
+                log.warn("Wrong mask format in RIB JSON: {}", mask1);
+                continue;
+            } catch (IllegalArgumentException e1) {
+                log.warn("Wrong prefix format in RIB JSON: {}", prefix1);
+                continue;
+            }
+
+            RibEntry rib = new RibEntry(inboundRouterId, nexthop);
+
+            try {
+                ribUpdates.put(new RibUpdate(Operation.UPDATE, p, rib));
+            } catch (InterruptedException e) {
+                log.debug("Interrupted while pushing onto update queue");
+            }
+        }
+    }
+
+    @Override
+    public void newRibUpdate(RibUpdate update) {
+        try {
+            ribUpdates.put(update);
+        } catch (InterruptedException e) {
+            log.debug("Interrupted while putting on ribUpdates queue", e);
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    public void processRibAdd(RibUpdate update) {
+        synchronized (this) {
+            Prefix prefix = update.getPrefix();
+
+            log.debug("Processing prefix add {}", prefix);
+
+            RibEntry rib = ptree.put(prefix, update.getRibEntry());
+
+            if (rib != null && !rib.equals(update.getRibEntry())) {
+                // There was an existing nexthop for this prefix. This update supersedes that,
+                // so we need to remove the old flows for this prefix from the switches
+                executeDeletePrefix(prefix, rib);
+            }
+
+            if (update.getRibEntry().getNextHop().equals(
+                    InetAddresses.forString("0.0.0.0"))) {
+                // Route originated by SDN domain
+                // We don't handle these at the moment
+                log.debug("Own route {} to {}", prefix,
+                        update.getRibEntry().getNextHop().getHostAddress());
+                return;
+            }
+
+            executeRibAdd(update);
+        }
+    }
+
+    private void executeRibAdd(RibUpdate update) {
+        // TODO: Fix the code below. Note that "deviceStorage" was removed.
+
+        /*
+        Prefix prefix = update.getPrefix();
+        RibEntry rib = update.getRibEntry();
+
+        InetAddress dstIpAddress = rib.getNextHop();
+        MACAddress nextHopMacAddress = null;
+
+        // See if we know the MAC address of the next hop
+        // TODO if we do not treat the next hop as a device in the future, we need to update this
+        IDeviceObject nextHopDevice =
+                deviceStorage.getDeviceByIP(InetAddresses.coerceToInteger(dstIpAddress));
+
+        if (nextHopDevice == null){
+            log.debug("NextHopDevice for IP: {} is null", dstIpAddress);
+            prefixesWaitingOnArp.put(dstIpAddress,
+                    new RibUpdate(Operation.UPDATE, prefix, rib));
+            proxyArp.sendArpRequest(dstIpAddress, this, true);
+            return;
+
+        }
+        nextHopMacAddress = MACAddress.valueOf(nextHopDevice.getMACAddress());
+
+        // Find the attachment point (egress interface) of the next hop
+        Interface egressInterface = null;
+        if (bgpPeers.containsKey(dstIpAddress)) {
+            //Route to a peer
+            log.debug("Route to peer {}", dstIpAddress);
+            BgpPeer peer = bgpPeers.get(dstIpAddress);
+            egressInterface = interfaces.get(peer.getInterfaceName());
+        } else {
+            //Route to non-peer
+            log.debug("Route to non-peer {}", dstIpAddress);
+            egressInterface = interfacePtree.match(
+                    new Prefix(dstIpAddress.getAddress(), 32));
+            if (egressInterface == null) {
+                log.warn("No outgoing interface found for {}", dstIpAddress.getHostAddress());
+                return;
+            }
+        }
+
+        if (nextHopMacAddress == null) {
+            prefixesWaitingOnArp.put(dstIpAddress,
+                    new RibUpdate(Operation.UPDATE, prefix, rib));
+            proxyArp.sendArpRequest(dstIpAddress, this, true);
+            return;
+        } else {
+            if (!bgpPeers.containsKey(dstIpAddress)) {
+                //If the prefix is for a non-peer we need to ensure there's a path,
+                //and push one if there isn't.
+                Path path = pushedPaths.get(dstIpAddress);
+                if (path == null) {
+                    path = new Path(egressInterface, dstIpAddress);
+                    calculateAndPushPath(path, nextHopMacAddress);
+                    pushedPaths.put(dstIpAddress, path);
+                }
+
+                path.incrementUsers();
+                prefixToPath.put(prefix, path);
+            }
+
+            //For all prefixes we need to add the first-hop mac-rewriting flows
+            addPrefixFlows(prefix, egressInterface, nextHopMacAddress);
+        }
+        */
+    }
+
+    /**
+     * Add a flow to match dst-IP prefix and rewrite MAC for one IP prefix
+     * to all other border switches.
+     */
+    private void addPrefixFlows(Prefix prefix, Interface egressInterface,
+                                MACAddress nextHopMacAddress) {
+        log.debug("Adding flows for prefix {}, next hop mac {}",
+                prefix, nextHopMacAddress);
+
+        FlowPath flowPath = new FlowPath();
+        flowPath.setInstallerId(new CallerId("SDNIP"));
+
+        // Set flowPath FlowPathType and FlowPathUserState
+        flowPath.setFlowPathType(FlowPathType.FP_TYPE_SHORTEST_PATH);
+        flowPath.setFlowPathUserState(FlowPathUserState.FP_USER_ADD);
+
+        // Insert dst-ip prefix based forwarding and MAC rewrite flow entry
+        // only to the first-host switches
+        FlowPathFlags flowPathFlags = new FlowPathFlags();
+        flowPathFlags.setFlags(FlowPathFlags.KEEP_ONLY_FIRST_HOP_ENTRY);
+        flowPath.setFlowPathFlags(flowPathFlags);
+
+        // Create the DataPath object: dstSwitchPort
+        SwitchPort dstPort =
+            new SwitchPort(new Dpid(egressInterface.getDpid()),
+                           new Port(egressInterface.getPort()));
+
+        // We only need one flow mod per switch, so pick one interface on each switch
+        Map<Long, Interface> srcInterfaces = new HashMap<Long, Interface>();
+        for (Interface intf : interfaces.values()) {
+            if (!srcInterfaces.containsKey(intf.getDpid())
+                    && !intf.equals(egressInterface)) {
+                srcInterfaces.put(intf.getDpid(), intf);
+            }
+        }
+        for (Interface srcInterface : srcInterfaces.values()) {
+
+            if (egressInterface.equals(srcInterface)) {
+                continue;
+            }
+
+            // Create flowPath FlowId
+            flowPath.setFlowId(new FlowId());
+
+            // Create DataPath object: srcSwitchPort
+            SwitchPort srcPort =
+                new SwitchPort(new Dpid(srcInterface.getDpid()),
+                               new Port(srcInterface.getPort()));
+
+            DataPath dataPath = new DataPath();
+            dataPath.setSrcPort(srcPort);
+            dataPath.setDstPort(dstPort);
+            flowPath.setDataPath(dataPath);
+
+            // Create flow path matching condition(s): IPv4 Prefix
+            FlowEntryMatch flowEntryMatch = new FlowEntryMatch();
+            flowEntryMatch.enableEthernetFrameType(Ethernet.TYPE_IPV4);
+            IPv4Net dstIPv4Net = new IPv4Net(prefix.toString());
+            flowEntryMatch.enableDstIPv4Net(dstIPv4Net);
+            flowPath.setFlowEntryMatch(flowEntryMatch);
+
+            /*
+             * Create the Flow Entry Action(s): dst-MAC rewrite action
+             */
+            FlowEntryActions flowEntryActions = new FlowEntryActions();
+            FlowEntryAction flowEntryAction1 = new FlowEntryAction();
+            flowEntryAction1.setActionSetEthernetDstAddr(nextHopMacAddress);
+            // flowEntryAction1.actionSetEthernetDstAddr(nextHopMacAddress);
+            flowEntryActions.addAction(flowEntryAction1);
+            flowPath.setFlowEntryActions(flowEntryActions);
+
+            // Flow Path installation, only to first hop switches
+            // TODO: Add the flow by using the new Path Intent framework
+            /*
+            if (flowManagerService.addFlow(flowPath) == null) {
+                log.error("Failed to install flow path to the first hop for " +
+                        "prefix: {}, nextHopMacAddress: {}", prefix.getAddress(),
+                        nextHopMacAddress);
+            }
+            else {
+                log.debug("Successfully installed flow path to the first hop " +
+                        "for prefix: {}, nextHopMacAddress: {}", prefix.getAddress(),
+                        nextHopMacAddress);
+
+                pushedFlowIds.put(prefix, flowPath.flowId());
+            }
+            */
+        }
+    }
+
+    public void processRibDelete(RibUpdate update) {
+        synchronized (this) {
+            Prefix prefix = update.getPrefix();
+
+            if (ptree.remove(prefix, update.getRibEntry())) {
+                /*
+                 * Only delete flows if an entry was actually removed from the tree.
+                 * If no entry was removed, the <prefix, nexthop> wasn't there so
+                 * it's probably already been removed and we don't need to do anything
+                 */
+                executeDeletePrefix(prefix, update.getRibEntry());
+            }
+        }
+    }
+
+    private void executeDeletePrefix(Prefix prefix, RibEntry ribEntry) {
+        deletePrefixFlows(prefix);
+
+        log.debug("Deleting {} to {}", prefix, ribEntry.getNextHop());
+
+        if (!bgpPeers.containsKey(ribEntry.getNextHop())) {
+            log.debug("Getting path for route with non-peer nexthop");
+            Path path = prefixToPath.remove(prefix);
+
+            if (path != null) {
+                //path could be null if we added to the Ptree but didn't push
+                //flows yet because we were waiting to resolve ARP
+
+                path.decrementUsers();
+                if (path.getUsers() <= 0 && !path.isPermanent()) {
+                    deletePath(path);
+                    pushedPaths.remove(path.getDstIpAddress());
+                }
+            }
+        }
+    }
+
+    // TODO have not tested this module
+    private void deletePrefixFlows(Prefix prefix) {
+        log.debug("Deleting flows for prefix {}", prefix);
+
+        //
+        // TODO: Delete the flow by using the new Path Intent framework
+        // NOTE: During the refactoring of the code below, if obtaining
+        // the values of the removed flowIds is needed, the first
+        // removeAll() statement should be replaced with the second removeAll()
+        // statement.
+        //
+        pushedFlowIds.removeAll(prefix);
+        /*
+        Collection<FlowId> flowIds = pushedFlowIds.removeAll(prefix);
+        for (FlowId flowId : flowIds) {
+            if (log.isTraceEnabled()) {
+                //Trace the flow status by flowPath in the switch before deleting it
+                log.trace("Pushing a DELETE flow mod to flowPath : {}",
+                        flowManagerService.getFlow(flowId).toString());
+            }
+
+            if( flowManagerService.deleteFlow(flowId))
+            {
+                log.debug("Successfully deleted FlowId: {}",flowId);
+            }
+            else
+            {
+                log.debug("Failed to delete FlowId: {}",flowId);
+            }
+        }
+        */
+    }
+
+    // TODO need to record the path and then delete here
+    private void deletePath(Path path) {
+        log.debug("Deleting flows for path to {}",
+                path.getDstIpAddress().getHostAddress());
+
+        // TODO need update
+        /*for (PushedFlowMod pfm : path.getFlowMods()) {
+            if (log.isTraceEnabled()) {
+                log.trace("Pushing a DELETE flow mod to {}, dst MAC {}",
+                        new Object[] {HexString.toHexString(pfm.getDpid()),
+                        HexString.toHexString(pfm.getFlowMod().getMatch().getDataLayerDestination())
+                });
+            }
+
+            sendDeleteFlowMod(pfm.getFlowMod(), pfm.getDpid());
+        }*/
+    }
+
+
+    //TODO test next-hop changes
+    //TODO check delete/add synchronization
+
+    /**
+     * On startup, we need to calculate a full mesh of paths between all gateway
+     * switches.
+     */
+    private void setupFullMesh() {
+        // TODO: Fix the code below. Note that "deviceStorage" was removed.
+
+        /*
+
+        //For each border router, calculate and install a path from every other
+        //border switch to said border router. However, don't install the entry
+        //in to the first hop switch, as we need to install an entry to rewrite
+        //for each prefix received. This will be done later when prefixes have
+        //actually been received.
+
+        for (BgpPeer peer : bgpPeers.values()) {
+            Interface peerInterface = interfaces.get(peer.getInterfaceName());
+
+            //We know there's not already a Path here pushed, because this is
+            //called before all other routing
+            Path path = new Path(peerInterface, peer.getIpAddress());
+            path.setPermanent();
+
+            //See if we know the MAC address of the peer. If not we can't
+            //do anything until we learn it
+            MACAddress macAddress = null;
+            IDeviceObject nextHopDevice =
+                    deviceStorage.getDeviceByIP(InetAddresses.coerceToInteger(peer.getIpAddress()));
+
+            if(nextHopDevice == null){
+                log.debug("There is no DeviceObject for {}", peer.getIpAddress().getHostAddress());
+                //Put in the pending paths list first
+                pathsWaitingOnArp.put(peer.getIpAddress(), path);
+                proxyArp.sendArpRequest(peer.getIpAddress(), this, true);
+                continue;
+            }
+
+            macAddress = MACAddress.valueOf(nextHopDevice.getMACAddress());
+
+            if (macAddress == null) {
+                log.debug("Don't know MAC for {}", peer.getIpAddress().getHostAddress());
+                //Put in the pending paths list first
+                pathsWaitingOnArp.put(peer.getIpAddress(), path);
+                proxyArp.sendArpRequest(peer.getIpAddress(), this, true);
+                continue;
+            }
+
+            //If we know the MAC, lets go ahead and push the paths to this peer
+            calculateAndPushPath(path, macAddress);
+        }
+        */
+    }
+
+    private void calculateAndPushPath(Path path, MACAddress dstMacAddress) {
+        Interface dstInterface = path.getDstInterface();
+
+        log.debug("Setting up path to {}, {}", path.getDstIpAddress().getHostAddress(),
+                dstMacAddress);
+
+        FlowPath flowPath = new FlowPath();
+
+        flowPath.setInstallerId(new CallerId("SDNIP"));
+
+        // Set flowPath FlowPathType and FlowPathUserState
+        flowPath.setFlowPathType(FlowPathType.FP_TYPE_SHORTEST_PATH);
+        flowPath.setFlowPathUserState(FlowPathUserState.FP_USER_ADD);
+
+        // Insert the dest-mac based forwarding flow entry to the non-first-hop switches
+        FlowPathFlags flowPathFlags = new FlowPathFlags();
+        flowPathFlags.setFlags(FlowPathFlags.DISCARD_FIRST_HOP_ENTRY);
+        flowPath.setFlowPathFlags(flowPathFlags);
+
+        // Create the DataPath object: dstSwitchPort
+        SwitchPort dstPort =
+            new SwitchPort(new Dpid(dstInterface.getDpid()),
+                           new Port(dstInterface.getPort()));
+
+        for (Interface srcInterface : interfaces.values()) {
+
+            if (dstInterface.equals(srcInterface)) {
+                continue;
+            }
+
+            // Create flowPath FlowId
+            flowPath.setFlowId(new FlowId());
+
+            // Create the DataPath object: srcSwitchPort
+            SwitchPort srcPort =
+                new SwitchPort(new Dpid(srcInterface.getDpid()),
+                               new Port(srcInterface.getPort()));
+
+            DataPath dataPath = new DataPath();
+            dataPath.setSrcPort(srcPort);
+            dataPath.setDstPort(dstPort);
+            flowPath.setDataPath(dataPath);
+
+            // Create the Flow Path Match condition(s)
+            FlowEntryMatch flowEntryMatch = new FlowEntryMatch();
+            flowEntryMatch.enableEthernetFrameType(Ethernet.TYPE_IPV4);
+            flowEntryMatch.enableDstMac(dstMacAddress);
+            flowPath.setFlowEntryMatch(flowEntryMatch);
+
+            // NOTE: No need to add ACTION_OUTPUT. It is implied when creating
+            // Shortest Path Flow, and is always the last action for the Flow Entries
+            log.debug("FlowPath of MAC based forwarding: {}", flowPath.toString());
+            // TODO: Add the flow by using the new Path Intent framework
+            /*
+            if (flowManagerService.addFlow(flowPath) == null) {
+                log.error("Failed to set up MAC based forwarding path to {}, {}",
+                        path.getDstIpAddress().getHostAddress(),dstMacAddress);
+            }
+            else {
+                log.debug("Successfully set up MAC based forwarding path to {}, {}",
+                        path.getDstIpAddress().getHostAddress(),dstMacAddress);
+            }
+            */
+        }
+    }
+
+    /**
+     * Proactively install all BGP traffic paths from BGP host attachment point
+     * in SDN network to all the virtual gateways to BGP peers in other networks.
+     */
+    private void setupBgpPaths() {
+
+        for (BgpPeer bgpPeer : bgpPeers.values()) {
+
+            FlowPath flowPath = new FlowPath();
+            flowPath.setInstallerId(new CallerId("SDNIP"));
+
+            // Set flowPath FlowPathType and FlowPathUserState
+            flowPath.setFlowPathType(FlowPathType.FP_TYPE_SHORTEST_PATH);
+            flowPath.setFlowPathUserState(FlowPathUserState.FP_USER_ADD);
+
+            // Install flow paths between BGPd and its peers
+            // There is no need to set the FlowPathFlags
+            flowPath.setFlowPathFlags(new FlowPathFlags(0));
+
+            Interface peerInterface = interfaces.get(bgpPeer.getInterfaceName());
+
+            // Create the Flow Path Match condition(s)
+            FlowEntryMatch flowEntryMatch = new FlowEntryMatch();
+            flowEntryMatch.enableEthernetFrameType(Ethernet.TYPE_IPV4);
+
+            // Match both source address and dest address
+            IPv4Net dstIPv4Net = new IPv4Net(bgpPeer.getIpAddress().getHostAddress() + "/32");
+            flowEntryMatch.enableDstIPv4Net(dstIPv4Net);
+
+            IPv4Net srcIPv4Net = new IPv4Net(peerInterface.getIpAddress().getHostAddress() + "/32");
+            flowEntryMatch.enableSrcIPv4Net(srcIPv4Net);
+
+            // Match TCP protocol
+            flowEntryMatch.enableIpProto(IPv4.PROTOCOL_TCP);
+
+            // Match destination TCP port
+            flowEntryMatch.enableDstTcpUdpPort(BGP_PORT);
+            flowPath.setFlowEntryMatch(flowEntryMatch);
+
+            /**
+             * Create the DataPath: BGP -> BGP peer
+             */
+            // Flow path for src-TCP-port
+            DataPath dataPath = new DataPath();
+
+            SwitchPort srcPort =
+                new SwitchPort(bgpdAttachmentPoint.dpid(),
+                               bgpdAttachmentPoint.port());
+            dataPath.setSrcPort(srcPort);
+
+            SwitchPort dstPort =
+                new SwitchPort(new Dpid(peerInterface.getDpid()),
+                               new Port(peerInterface.getSwitchPort().port()));
+            dataPath.setDstPort(dstPort);
+
+            flowPath.setDataPath(dataPath);
+
+            // TODO: Add the flow by using the new Path Intent framework
+            /*
+            if (flowManagerService.addFlow(flowPath) == null) {
+                log.error("Failed to set up path BGP -> peer {}"+"; dst-TCP-port:179",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            else {
+                log.debug("Successfully set up path BGP -> peer {}"+"; dst-TCP-port:179",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            */
+
+            // Disable dst-TCP-port, and set src-TCP-port
+            flowEntryMatch.disableDstTcpUdpPort();
+            flowEntryMatch.enableSrcTcpUdpPort(BGP_PORT);
+            flowPath.setFlowEntryMatch(flowEntryMatch);
+
+            // Create a new FlowId
+            flowPath.setFlowId(new FlowId());
+
+            // TODO: Add the flow by using the new Path Intent framework
+            /*
+            if (flowManagerService.addFlow(flowPath) == null) {
+                log.error("Failed to set up path BGP -> Peer {}" + "; src-TCP-port:179",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            else {
+                log.debug("Successfully set up path BGP -> Peer {}" + "; src-TCP-port:179",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            */
+
+            /**
+             * Create the DataPath: BGP <-BGP peer
+             */
+            // Reversed BGP flow path for src-TCP-port
+            flowPath.setFlowId(new FlowId());
+
+            DataPath reverseDataPath = new DataPath();
+
+            SwitchPort reverseDstPort =
+                new SwitchPort(bgpdAttachmentPoint.dpid(),
+                               bgpdAttachmentPoint.port());
+            reverseDataPath.setDstPort(reverseDstPort);
+
+            SwitchPort reverseSrcPort =
+                new SwitchPort(new Dpid(peerInterface.getDpid()),
+                               new Port(peerInterface.getSwitchPort().port()));
+            reverseDataPath.setSrcPort(reverseSrcPort);
+            flowPath.setDataPath(reverseDataPath);
+
+            // Reverse the dst IP and src IP addresses
+            flowEntryMatch.enableDstIPv4Net(srcIPv4Net);
+            flowEntryMatch.enableSrcIPv4Net(dstIPv4Net);
+            flowPath.setFlowEntryMatch(flowEntryMatch);
+
+            log.debug("Reversed BGP FlowPath: {}", flowPath.toString());
+
+            // TODO: Add the flow by using the new Path Intent framework
+            /*
+            if (flowManagerService.addFlow(flowPath) == null) {
+
+                log.error("Failed to set up path BGP <- Peer {}" + "; src-TCP-port:179",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            else {
+                log.debug("Successfully set up path BGP <- Peer {}" + "; src-TCP-port:179",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            */
+
+            // Reversed BGP flow path for dst-TCP-port
+            flowPath.setFlowId(new FlowId());
+
+            // Disable src-TCP-port, and set the dst-TCP-port
+            flowEntryMatch.disableSrcTcpUdpPort();
+            flowEntryMatch.enableDstTcpUdpPort(BGP_PORT);
+            flowPath.setFlowEntryMatch(flowEntryMatch);
+
+            log.debug("Reversed BGP FlowPath: {}", flowPath.toString());
+
+            // TODO: Add the flow by using the new Path Intent framework
+            /*
+            if (flowManagerService.addFlow(flowPath) == null) {
+                log.error("Failed to setting up path BGP <- Peer {}" + "; dst-TCP-port:179",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            else {
+                log.debug("Successfully setting up path BGP <- Peer {}" + "; dst-TCP-port:179",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            */
+
+            /**
+             * ICMP paths between BGPd and its peers
+             */
+            // match ICMP protocol BGP <- Peer
+            flowPath.setFlowId(new FlowId());
+
+            flowEntryMatch.enableIpProto(IPv4.PROTOCOL_ICMP);
+            flowEntryMatch.disableSrcTcpUdpPort();
+            flowEntryMatch.disableDstTcpUdpPort();
+
+            flowPath.setFlowEntryMatch(flowEntryMatch);
+
+            flowPath.setDataPath(reverseDataPath);
+
+            log.debug("Reversed ICMP FlowPath: {}", flowPath.toString());
+
+            // TODO: Add the flow by using the new Path Intent framework
+            /*
+            if (flowManagerService.addFlow(flowPath) == null) {
+
+                log.error("Failed to set up ICMP path BGP <- Peer {}",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            else {
+                log.debug("Successfully set up ICMP path BGP <- Peer {}",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            */
+
+            // match ICMP protocol BGP -> Peer
+            flowPath.setFlowId(new FlowId());
+
+            flowEntryMatch.enableDstIPv4Net(dstIPv4Net);
+            flowEntryMatch.enableSrcIPv4Net(srcIPv4Net);
+            flowPath.setFlowEntryMatch(flowEntryMatch);
+
+            flowPath.setDataPath(dataPath);
+
+            log.debug("ICMP flowPath: {}", flowPath.toString());
+
+            // TODO: Add the flow by using the new Path Intent framework
+            /*
+            if (flowManagerService.addFlow(flowPath) == null) {
+
+                log.error("Failed to set up ICMP path BGP -> Peer {}",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            else {
+                log.debug("Successfully set up ICMP path BGP -> Peer {}",
+                        bgpPeer.getIpAddress().getHostAddress());
+            }
+            */
+        }
+    }
+
+    @Override
+    public void arpResponse(InetAddress ipAddress, MACAddress macAddress) {
+        log.debug("Received ARP response: {} => {}",
+                ipAddress.getHostAddress(), macAddress);
+
+        /*
+         * We synchronize on this to prevent changes to the ptree while we're pushing
+         * flows to the switches. If the ptree changes, the ptree and switches
+         * could get out of sync.
+         */
+        synchronized (this) {
+            Path path = pathsWaitingOnArp.remove(ipAddress);
+
+            if (path != null) {
+                log.debug("Pushing path to {} at {} on {}", new Object[]{
+                        path.getDstIpAddress().getHostAddress(), macAddress,
+                        path.getDstInterface().getSwitchPort()});
+                // These paths should always be to BGP peers. Paths to non-peers are
+                // handled once the first prefix is ready to push
+                if (pushedPaths.containsKey(path.getDstIpAddress())) {
+                    // A path already got pushed to this endpoint while we were waiting
+                    // for ARP. We'll copy over the permanent attribute if it is set on this path.
+                    if (path.isPermanent()) {
+                        pushedPaths.get(path.getDstIpAddress()).setPermanent();
+                    }
+                } else {
+                    calculateAndPushPath(path, macAddress);
+                    pushedPaths.put(path.getDstIpAddress(), path);
+                }
+            }
+
+            Set<RibUpdate> prefixesToPush = prefixesWaitingOnArp.removeAll(ipAddress);
+
+            for (RibUpdate update : prefixesToPush) {
+                // These will always be adds
+
+                RibEntry rib = ptree.lookup(update.getPrefix());
+                if (rib != null && rib.equals(update.getRibEntry())) {
+                    log.debug("Pushing prefix {} next hop {}", update.getPrefix(),
+                            rib.getNextHop().getHostAddress());
+                    // We only push prefix flows if the prefix is still in the ptree
+                    // and the next hop is the same as our update. The prefix could
+                    // have been removed while we were waiting for the ARP, or the
+                    // next hop could have changed.
+                    executeRibAdd(update);
+                } else {
+                    log.debug("Received ARP response, but {},{} is no longer in ptree",
+                            update.getPrefix(), update.getRibEntry());
+                }
+            }
+        }
+    }
+
+    // TODO wait the priority module of the flow Manager
+    private void setupArpFlows() {
+        OFMatch match = new OFMatch();
+        match.setDataLayerType(Ethernet.TYPE_ARP);
+        match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
+
+        OFFlowMod fm = new OFFlowMod();
+        fm.setMatch(match);
+
+        OFActionOutput action = new OFActionOutput();
+        action.setPort(OFPort.OFPP_CONTROLLER.getValue());
+        action.setMaxLength((short) 0xffff);
+        List<OFAction> actions = new ArrayList<OFAction>(1);
+        actions.add(action);
+        fm.setActions(actions);
+
+        fm.setIdleTimeout((short) 0)
+                .setHardTimeout((short) 0)
+                .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+                .setCookie(0)
+                .setCommand(OFFlowMod.OFPFC_ADD)
+                .setPriority(ARP_PRIORITY)
+                .setLengthU(OFFlowMod.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH);
+
+        for (String strdpid : switches) {
+            flowCache.write(HexString.toLong(strdpid), fm);
+        }
+    }
+
+    // TODO need update, waiting for the priority feature from flow Manager
+    private void setupDefaultDropFlows() {
+        OFFlowMod fm = new OFFlowMod();
+        fm.setMatch(new OFMatch());
+        fm.setActions(new ArrayList<OFAction>()); // No action means drop
+
+        fm.setIdleTimeout((short) 0)
+                .setHardTimeout((short) 0)
+                .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+                .setCookie(0)
+                .setCommand(OFFlowMod.OFPFC_ADD)
+                .setPriority((short) 0)
+                .setLengthU(OFFlowMod.MINIMUM_LENGTH);
+
+        OFFlowMod fmLLDP;
+        OFFlowMod fmBDDP;
+        try {
+            fmLLDP = fm.clone();
+            fmBDDP = fm.clone();
+        } catch (CloneNotSupportedException e1) {
+            log.error("Error cloning flow mod", e1);
+            return;
+        }
+
+        OFMatch matchLLDP = new OFMatch();
+        matchLLDP.setDataLayerType((short) 0x88cc);
+        matchLLDP.setWildcards(matchLLDP.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
+        fmLLDP.setMatch(matchLLDP);
+
+        OFMatch matchBDDP = new OFMatch();
+        matchBDDP.setDataLayerType((short) 0x8942);
+        matchBDDP.setWildcards(matchBDDP.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
+        fmBDDP.setMatch(matchBDDP);
+
+        OFActionOutput action = new OFActionOutput();
+        action.setPort(OFPort.OFPP_CONTROLLER.getValue());
+        action.setMaxLength((short) 0xffff);
+        List<OFAction> actions = new ArrayList<OFAction>(1);
+        actions.add(action);
+
+        fmLLDP.setActions(actions);
+        fmBDDP.setActions(actions);
+
+        fmLLDP.setPriority(ARP_PRIORITY);
+        fmLLDP.setLengthU(OFFlowMod.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH);
+        fmBDDP.setPriority(ARP_PRIORITY);
+        fmBDDP.setLengthU(OFFlowMod.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH);
+
+        List<OFFlowMod> flowModList = new ArrayList<OFFlowMod>(3);
+        flowModList.add(fm);
+        flowModList.add(fmLLDP);
+        flowModList.add(fmBDDP);
+
+        for (String strdpid : switches) {
+            flowCache.write(HexString.toLong(strdpid), flowModList);
+        }
+    }
+
+    private void beginRouting() {
+        log.debug("Topology is now ready, beginning routing function");
+        // TODO: Fix for the new Topology Network Graph
+        // topology = topologyNetService.newDatabaseTopology();
+
+        // Wait Pavlin's API. We need the following functions.
+        /*setupArpFlows();
+        setupDefaultDropFlows();*/
+
+        setupBgpPaths();
+        setupFullMesh();
+
+        //Suppress link discovery on external-facing router ports
+        for (Interface intf : interfaces.values()) {
+            linkDiscoveryService.addToSuppressLLDPs(intf.getDpid(), intf.getPort());
+        }
+
+        bgpUpdatesExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                doUpdatesThread();
+            }
+        });
+    }
+
+    // Before inserting the paths for BGP traffic, we should check whether
+    // all the switches in the configuration file are discovered by ONOS
+    private void checkSwitchesConnected() {
+        // TODO: Fix the code below after topoSwitchSerice was removed
+        /*
+        for (String dpid : switches) {
+
+            Iterator<ISwitchObject> activeSwitches = topoSwitchService.
+                    getActiveSwitches().iterator();
+            while(activeSwitches.hasNext())
+            {
+                ISwitchObject switchObject = activeSwitches.next();
+                if (switchObject.getDPID().equals(dpid)) {
+                    break;
+                }
+                if(activeSwitches.hasNext() == false) {
+                    log.debug("Not all switches are here yet");
+                    return;
+                }
+            }
+        }
+        switchesConnected = true;
+        */
+    }
+
+    // Actually we only need to go half way round to verify full mesh connectivity
+    private void checkTopologyReady() {
+        for (Interface dstInterface : interfaces.values()) {
+            for (Interface srcInterface : interfaces.values()) {
+                if (dstInterface.equals(srcInterface)) {
+                    continue;
+                }
+
+                // TODO: Fix for the new Topology Network Graph
+                /*
+                DataPath shortestPath = topologyNetService.getDatabaseShortestPath(
+                        srcInterface.getSwitchPort(), dstInterface.getSwitchPort());
+
+                if (shortestPath == null){
+                    log.debug("Shortest path between {} and {} not found",
+                            srcInterface.getSwitchPort(), dstInterface.getSwitchPort());
+                    return;
+                }
+                */
+            }
+        }
+        topologyReady = true;
+    }
+
+    private void checkStatus() {
+        if (!switchesConnected) {
+            checkSwitchesConnected();
+        }
+        boolean oldTopologyReadyStatus = topologyReady;
+        if (switchesConnected && !topologyReady) {
+            checkTopologyReady();
+        }
+        if (!oldTopologyReadyStatus && topologyReady) {
+            beginRouting();
+        }
+    }
+
+    private void doUpdatesThread() {
+        boolean interrupted = false;
+        try {
+            while (true) {
+                try {
+                    RibUpdate update = ribUpdates.take();
+                    switch (update.getOperation()) {
+                        case UPDATE:
+                            if (validateUpdate(update)) {
+                                processRibAdd(update);
+                            } else {
+                                log.debug("Rib UPDATE out of order: {} via {}",
+                                        update.getPrefix(), update.getRibEntry().getNextHop());
+                            }
+                            break;
+                        case DELETE:
+                            if (validateUpdate(update)) {
+                                processRibDelete(update);
+                            } else {
+                                log.debug("Rib DELETE out of order: {} via {}",
+                                        update.getPrefix(), update.getRibEntry().getNextHop());
+                            }
+                            break;
+                        default:
+                            log.error("Unknown operation {}", update.getOperation());
+                            break;
+                    }
+                } catch (InterruptedException e) {
+                    log.debug("Interrupted while taking from updates queue", e);
+                    interrupted = true;
+                } catch (Exception e) {
+                    log.debug("exception", e);
+                }
+            }
+        } finally {
+            if (interrupted) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    private boolean validateUpdate(RibUpdate update) {
+        RibEntry newEntry = update.getRibEntry();
+        RibEntry oldEntry = ptree.lookup(update.getPrefix());
+
+        //If there is no existing entry we must assume this is the most recent
+        //update. However this might not always be the case as we might have a
+        //POST then DELETE reordering.
+        //if (oldEntry == null || !newEntry.getNextHop().equals(oldEntry.getNextHop())) {
+        if (oldEntry == null) {
+            return true;
+        }
+
+        // This handles the case where routes are gathered in the initial
+        // request because they don't have sequence number info
+        if (newEntry.getSysUpTime() == -1 && newEntry.getSequenceNum() == -1) {
+            return true;
+        }
+
+        if (newEntry.getSysUpTime() > oldEntry.getSysUpTime()) {
+            return true;
+        }
+
+        return newEntry.getSysUpTime() == oldEntry.getSysUpTime() &&
+               newEntry.getSequenceNum() > oldEntry.getSequenceNum();
+    }
+
+    // The code below should be reimplemented after removal of Floodlight's
+    // ITopologyService API. It should be implemented on top of network graph
+    // notifications. (It was pretty hacky anyway...)
+    /*
+    @Override
+    public void topologyChanged() {
+        if (topologyReady) {
+            return;
+        }
+
+        boolean refreshNeeded = false;
+        for (LDUpdate ldu : topologyService.getLastLinkUpdates()){
+            if (!ldu.getOperation().equals(ILinkDiscovery.UpdateOperation.LINK_UPDATED)){
+                //We don't need to recalculate anything for just link updates
+                //They happen very frequently
+                refreshNeeded = true;
+            }
+
+            log.debug("Topo change {}", ldu.getOperation());
+
+            if (ldu.getOperation().equals(ILinkDiscovery.UpdateOperation.LINK_ADDED)){
+                synchronized (linkUpdates) {
+                    linkUpdates.add(ldu);
+                }
+            }
+        }
+
+        if (refreshNeeded && !topologyReady){
+            topologyChangeDetectorTask.reschedule(TOPO_DETECTION_WAIT, TimeUnit.SECONDS);
+        }
+    }
+    */
+
+    @Override
+    public void addedSwitch(IOFSwitch sw) {
+        if (!topologyReady) {
+            sw.clearAllFlowMods();
+        }
+
+        flowCache.switchConnected(sw);
+    }
+
+    @Override
+    public void removedSwitch(IOFSwitch sw) {
+        // Not used
+    }
+
+    @Override
+    public void switchPortChanged(Long switchId) {
+        // Not used
+    }
+
+    @Override
+    public String getName() {
+        return "SdnIp";
+    }
+
+    /*
+     * IConfigInfoService methods
+     */
+
+    @Override
+    public boolean isInterfaceAddress(InetAddress address) {
+        Interface intf = interfacePtree.match(new Prefix(address.getAddress(), 32));
+        return (intf != null && intf.getIpAddress().equals(address));
+    }
+
+    @Override
+    public boolean inConnectedNetwork(InetAddress address) {
+        Interface intf = interfacePtree.match(new Prefix(address.getAddress(), 32));
+        return (intf != null && !intf.getIpAddress().equals(address));
+    }
+
+    @Override
+    public boolean fromExternalNetwork(long inDpid, short inPort) {
+        for (Interface intf : interfaces.values()) {
+            if (intf.getDpid() == inDpid && intf.getPort() == inPort) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Interface getOutgoingInterface(InetAddress dstIpAddress) {
+        return interfacePtree.match(new Prefix(dstIpAddress.getAddress(), 32));
+    }
+
+    @Override
+    public boolean hasLayer3Configuration() {
+        return !interfaces.isEmpty();
+    }
+
+    @Override
+    public MACAddress getRouterMacAddress() {
+        return bgpdMacAddress;
+    }
+
+    @Override
+    public short getVlan() {
+        return vlan;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/web/IncomingRequestResource.java b/src/main/java/net/onrc/onos/apps/sdnip/web/IncomingRequestResource.java
new file mode 100644
index 0000000..705c5f1
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/web/IncomingRequestResource.java
@@ -0,0 +1,215 @@
+package net.onrc.onos.apps.sdnip.web;
+
+import java.util.Iterator;
+
+import net.onrc.onos.apps.sdnip.ISdnIpService;
+import net.onrc.onos.apps.sdnip.IPatriciaTree;
+import net.onrc.onos.apps.sdnip.Prefix;
+import net.onrc.onos.apps.sdnip.RestClient;
+import net.onrc.onos.apps.sdnip.RibEntry;
+import net.onrc.onos.apps.sdnip.RibUpdate;
+import net.onrc.onos.apps.sdnip.RibUpdate.Operation;
+
+import org.restlet.resource.Delete;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * REST resource that handles REST calls from BGPd. This is the interface BGPd
+ * uses to push RIB entries (routes) to SDN-IP.
+ */
+public class IncomingRequestResource extends ServerResource {
+    private static final Logger log = LoggerFactory.getLogger(IncomingRequestResource.class);
+
+    /**
+     * Gets the contents of SDN-IP's route table.
+     *
+     * @return the contents of SDN-IP's route table formatted as JSON
+     */
+    @Get
+    public String handleGetMethod() {
+        String dest = (String) getRequestAttributes().get("dest");
+        StringBuilder output = new StringBuilder(80);
+        ISdnIpService sdnIp = (ISdnIpService) getContext()
+                .getAttributes().
+                get(ISdnIpService.class.getCanonicalName());
+
+        if (dest == null) {
+            IPatriciaTree<RibEntry> ptree = sdnIp.getPtree();
+            output.append("{\n  \"rib\": [\n");
+            boolean printed = false;
+
+            synchronized (ptree) {
+                Iterator<IPatriciaTree.Entry<RibEntry>> it = ptree.iterator();
+                while (it.hasNext()) {
+                    IPatriciaTree.Entry<RibEntry> entry = it.next();
+
+                    if (printed) {
+                        output.append(",\n");
+                    }
+
+                    output.append("    {\"prefix\": \"");
+                    output.append(entry.getPrefix());
+                    output.append("\", \"nexthop\": \"");
+                    output.append(entry.getValue().getNextHop().getHostAddress());
+                    output.append("\"}");
+
+                    printed = true;
+                }
+            }
+
+            output.append("\n  ]\n}\n");
+        } else {
+            // TODO Needs to be changed to use the new RestClient.get().
+
+            // the dest here refers to router-id
+            // bgpdRestIp includes port number, such as 1.1.1.1:8080
+            String bgpdRestIp = sdnIp.getBgpdRestIp();
+            String url = "http://" + bgpdRestIp + "/wm/bgp/" + dest;
+
+            // Doesn't actually do anything with the response
+            RestClient.get(url);
+
+            output.append("Get rib from bgpd finished!\n");
+        }
+
+        return output.toString();
+    }
+
+    /**
+     * Handler to receive a new RIB entry. The details of the RIB entry are
+     * given as part of the URL.
+     *
+     * @return a String describing the result of the action
+     */
+    @Post
+    public String handlePostMethod() {
+        ISdnIpService sdnIp = (ISdnIpService) getContext()
+                .getAttributes().
+                get(ISdnIpService.class.getCanonicalName());
+
+        String strSysuptime = (String) getRequestAttributes().get("sysuptime");
+        String strSequence = (String) getRequestAttributes().get("sequence");
+        String routerId = (String) getRequestAttributes().get("routerid");
+        String prefix = (String) getRequestAttributes().get("prefix");
+        String mask = (String) getRequestAttributes().get("mask");
+        String nexthop = (String) getRequestAttributes().get("nexthop");
+        String capability = (String) getRequestAttributes().get("capability");
+
+        log.debug("sysuptime: {}", strSysuptime);
+        log.debug("sequence: {}", strSequence);
+
+        String reply = "";
+
+        if (capability == null) {
+            // this is a prefix add
+            Prefix p;
+            long sysUpTime, sequenceNum;
+            try {
+                p = new Prefix(prefix, Integer.valueOf(mask));
+                sysUpTime = Long.parseLong(strSysuptime);
+                sequenceNum = Long.parseLong(strSequence);
+            } catch (NumberFormatException e) {
+                reply = "[POST: mask format is wrong]";
+                log.info(reply);
+                return reply + "\n";
+            } catch (IllegalArgumentException e1) {
+                reply = "[POST: prefix format is wrong]";
+                log.info(reply);
+                return reply + "\n";
+            }
+
+            RibEntry rib = new RibEntry(routerId, nexthop, sysUpTime,
+                    sequenceNum);
+
+            sdnIp.newRibUpdate(new RibUpdate(Operation.UPDATE, p, rib));
+
+            reply = "[POST: " + prefix + "/" + mask + ":" + nexthop + "]";
+            log.info(reply);
+        } else {
+            reply = "[POST-capability: " + capability + "]\n";
+            log.info(reply);
+            // to store the number in the top node of the Ptree
+        }
+        /*else if ("1".equals(capability)) {
+            reply = "[POST-capability: " + capability + "]\n";
+            log.info(reply);
+            // to store the number in the top node of the Ptree
+        }*/
+
+        return reply + "\n";
+    }
+
+    /**
+     * Handler to remove a RIB entry from ONOS's route table. The details of
+     * the route to remove are passed as part of the URL.
+     *
+     * @return a String describing the result of the action
+     */
+    @Delete
+    public String handleDeleteMethod() {
+        ISdnIpService sdnIp = (ISdnIpService) getContext()
+                .getAttributes().
+                get(ISdnIpService.class.getCanonicalName());
+
+        String strSysuptime = (String) getRequestAttributes().get("sysuptime");
+        String strSequence = (String) getRequestAttributes().get("sequence");
+        String routerId = (String) getRequestAttributes().get("routerid");
+        String prefix = (String) getRequestAttributes().get("prefix");
+        String mask = (String) getRequestAttributes().get("mask");
+        String nextHop = (String) getRequestAttributes().get("nexthop");
+        String capability = (String) getRequestAttributes().get("capability");
+
+        log.debug("sysuptime: {}", strSysuptime);
+        log.debug("sequence: {}", strSequence);
+
+        //String reply = "";
+        StringBuilder replyStringBuilder = new StringBuilder(80);
+
+        if (capability == null) {
+            // this is a prefix delete
+            Prefix p;
+            long sysUpTime, sequenceNum;
+            try {
+                p = new Prefix(prefix, Integer.valueOf(mask));
+                sysUpTime = Long.parseLong(strSysuptime);
+                sequenceNum = Long.parseLong(strSequence);
+            } catch (NumberFormatException e) {
+                String reply = "[DELE: mask format is wrong]";
+                log.info(reply);
+                return reply + "\n";
+            } catch (IllegalArgumentException e1) {
+                String reply = "[DELE: prefix format is wrong]";
+                log.info(reply);
+                return reply + "\n";
+            }
+
+            RibEntry r = new RibEntry(routerId, nextHop, sysUpTime, sequenceNum);
+
+            sdnIp.newRibUpdate(new RibUpdate(Operation.DELETE, p, r));
+
+            replyStringBuilder.append("[DELE: ")
+                .append(prefix)
+                .append('/')
+                .append(mask)
+                .append(':')
+                .append(nextHop)
+                .append(']');
+        } else {
+            // clear the local rib: Ptree
+            sdnIp.clearPtree();
+            replyStringBuilder.append("[DELE-capability: ")
+                    .append(capability)
+                    .append("; The local RibEntry is cleared!]\n");
+
+            // to store the number in the top node of the Ptree
+        }
+
+        log.info(replyStringBuilder.toString());
+        replyStringBuilder.append('\n');
+        return replyStringBuilder.toString();
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/web/OutgoingRequestResource.java b/src/main/java/net/onrc/onos/apps/sdnip/web/OutgoingRequestResource.java
new file mode 100644
index 0000000..772bf7d
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/web/OutgoingRequestResource.java
@@ -0,0 +1,88 @@
+package net.onrc.onos.apps.sdnip.web;
+
+import net.onrc.onos.apps.sdnip.ISdnIpService;
+import net.onrc.onos.apps.sdnip.RestClient;
+
+import org.restlet.resource.Delete;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * REST handler for sending commands to SDN-IP. This interface is intended for
+ * operators or developers to change the route table of BGPd. There are
+ * interfaces for both sending new routes and deleting routes. This is
+ * not intended to be used during general operation. It is to have a way to
+ * influence BGPd's behavior for debugging.
+ */
+public class OutgoingRequestResource extends ServerResource {
+    private static final Logger log = LoggerFactory.getLogger(OutgoingRequestResource.class);
+
+    /**
+     * Handles a REST call to SDN-IP which gives a command to send a new route
+     * to BGPd.
+     *
+     * @return a String describing the result of the operation
+     */
+    @Post
+    public String handlePostMethod() {
+
+        ISdnIpService sdnIp = (ISdnIpService) getContext().getAttributes().
+                get(ISdnIpService.class.getCanonicalName());
+
+        String routerId = (String) getRequestAttributes().get("routerid");
+        String prefix = (String) getRequestAttributes().get("prefix");
+        String mask = (String) getRequestAttributes().get("mask");
+        String nexthop = (String) getRequestAttributes().get("nexthop");
+
+        String bgpdRestIp = sdnIp.getBgpdRestIp();
+
+        // bgpdRestIp includes port number, e.g. 1.1.1.1:8080
+        RestClient.post("http://" + bgpdRestIp + "/wm/bgp/" + routerId + "/" + prefix + "/"
+                + mask + "/" + nexthop);
+
+        String reply = "";
+        reply = "[POST: " + prefix + "/" + mask + ":" + nexthop + "/synch]";
+        log.info(reply);
+
+        return reply + "\n";
+
+    }
+
+    /**
+     * Handles a REST call to SDN-IP which gives a command to BGPd to delete a
+     * route from its route table.
+     *
+     * @return a String description of the result of the operation
+     */
+    @Delete
+    public String handleDeleteMethod() {
+        ISdnIpService sdnIp = (ISdnIpService) getContext().getAttributes().
+                get(ISdnIpService.class.getCanonicalName());
+
+        String routerId = (String) getRequestAttributes().get("routerid");
+        String prefix = (String) getRequestAttributes().get("prefix");
+        String mask = (String) getRequestAttributes().get("mask");
+        String nextHop = (String) getRequestAttributes().get("nexthop");
+
+        StringBuilder reply = new StringBuilder();
+
+        String bgpdRestIp = sdnIp.getBgpdRestIp();
+
+        RestClient.delete("http://" + bgpdRestIp + "/wm/bgp/" + routerId + "/" + prefix + "/"
+                + mask + "/" + nextHop);
+
+        reply.append("[DELE: ")
+             .append(prefix)
+             .append('/')
+             .append(mask)
+             .append(':')
+             .append(nextHop)
+             .append("/synch]");
+
+        log.info(reply.toString());
+
+        return reply.append("\n").toString();
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/sdnip/web/SdnIpWebRoutable.java b/src/main/java/net/onrc/onos/apps/sdnip/web/SdnIpWebRoutable.java
new file mode 100644
index 0000000..8994f18
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/sdnip/web/SdnIpWebRoutable.java
@@ -0,0 +1,28 @@
+package net.onrc.onos.apps.sdnip.web;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+/**
+ * REST URL router for SDN-IP REST calls.
+ */
+public class SdnIpWebRoutable implements RestletRoutable {
+    @Override
+    public Restlet getRestlet(Context context) {
+        Router router = new Router(context);
+        router.attach("/json", IncomingRequestResource.class);
+        router.attach("/rib/{dest}", IncomingRequestResource.class);
+        router.attach("/{sysuptime}/{sequence}/{routerid}/{prefix}/{mask}/{nexthop}", IncomingRequestResource.class);
+        router.attach("/{routerid}/{prefix}/{mask}/{nexthop}/synch", OutgoingRequestResource.class);
+        router.attach("/{routerid}/{capability}", IncomingRequestResource.class);
+        return router;
+    }
+
+    @Override
+    public String basePath() {
+        return "/wm/bgp";
+    }
+}