Create a formal interface between the RIB and FIB.

 * IntentSynchronizer implements a more generalized FibListener interface
 * Updates to the FIB are signalled with FibUpdate to any FibListeners
 * generateRouteIntent logic has been pushed down into the IntentSynchronizer

Change-Id: I6f0ccfd52ee4e16ce9974af5ee549d4ede6c2d0e
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/FibEntry.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/FibEntry.java
new file mode 100644
index 0000000..988b49a
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/FibEntry.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.sdnip;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+
+/**
+ * An entry in the Forwarding Information Base (FIB).
+ */
+public class FibEntry {
+
+    private final IpPrefix prefix;
+    private final IpAddress nextHopIp;
+    private final MacAddress nextHopMac;
+
+    /**
+     * Creates a new FIB entry.
+     *
+     * @param prefix IP prefix of the FIB entry
+     * @param nextHopIp IP address of the next hop
+     * @param nextHopMac MAC address of the next hop
+     */
+    public FibEntry(IpPrefix prefix, IpAddress nextHopIp, MacAddress nextHopMac) {
+        this.prefix = prefix;
+        this.nextHopIp = nextHopIp;
+        this.nextHopMac = nextHopMac;
+    }
+
+    /**
+     * Returns the IP prefix of the FIB entry.
+     *
+     * @return the IP prefix
+     */
+    public IpPrefix prefix() {
+        return prefix;
+    }
+
+    /**
+     * Returns the IP address of the next hop.
+     *
+     * @return the IP address
+     */
+    public IpAddress nextHopIp() {
+        return nextHopIp;
+    }
+
+    /**
+     * Returns the MAC address of the next hop.
+     *
+     * @return the MAC address
+     */
+    public MacAddress nextHopMac() {
+        return nextHopMac;
+    }
+}
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/FibListener.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/FibListener.java
new file mode 100644
index 0000000..13674e3
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/FibListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.sdnip;
+
+import java.util.Collection;
+
+/**
+ * A component that is able to process Forwarding Information Base (FIB) updates.
+ */
+public interface FibListener {
+
+    /**
+     * Signals the FIB component of changes to the FIB.
+     *
+     * @param updates FIB updates of the UDPATE type
+     * @param withdraws FIB updates of the WITHDRAW type
+     */
+    // TODO this interface should use only one collection when we have the new
+    // intent key API
+    void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws);
+
+}
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/FibUpdate.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/FibUpdate.java
new file mode 100644
index 0000000..a3d8c3c
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/FibUpdate.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.sdnip;
+
+/**
+ * Represents a change to the Forwarding Information Base (FIB).
+ */
+public class FibUpdate {
+
+    /**
+     * Specifies the type of the FIB update.
+     */
+    public enum Type {
+        /**
+         * The update contains a new or updated FIB entry for a prefix.
+         */
+        UPDATE,
+
+        /**
+         * The update signals that a prefix should be removed from the FIB.
+         */
+        DELETE
+    }
+
+    private final Type type;
+    private final FibEntry entry;
+
+    /**
+     * Creates a new FIB update.
+     *
+     * @param type type of the update
+     * @param entry FIB entry describing the update
+     */
+    public FibUpdate(Type type, FibEntry entry) {
+        this.type = type;
+        this.entry = entry;
+    }
+
+    /**
+     * Returns the type of the update.
+     *
+     * @return update type
+     */
+    public Type type() {
+        return type;
+    }
+
+    /**
+     * Returns the FIB entry which contains update information.
+     *
+     * @return the FIB entry
+     */
+    public FibEntry entry() {
+        return entry;
+    }
+}
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
index 0a0e569..9f3c872 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/IntentSynchronizer.java
@@ -15,22 +15,19 @@
  */
 package org.onosproject.sdnip;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Semaphore;
-
-import org.apache.commons.lang3.tuple.Pair;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criteria.IPCriterion;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.intent.Intent;
@@ -39,16 +36,32 @@
 import org.onosproject.net.intent.IntentState;
 import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.sdnip.config.BgpPeer;
+import org.onosproject.sdnip.config.Interface;
+import org.onosproject.sdnip.config.SdnIpConfigurationService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+
+import static com.google.common.base.Preconditions.checkArgument;
 
 /**
  * Synchronizes intents between the in-memory intent store and the
  * IntentService.
  */
-public class IntentSynchronizer {
+public class IntentSynchronizer implements FibListener {
     private static final Logger log =
         LoggerFactory.getLogger(IntentSynchronizer.class);
 
@@ -65,18 +78,28 @@
     private volatile boolean isElectedLeader = false;
     private volatile boolean isActivatedLeader = false;
 
+    private final SdnIpConfigurationService configService;
+    private final InterfaceService interfaceService;
+
     /**
      * Class constructor.
      *
      * @param appId the Application ID
      * @param intentService the intent service
+     * @param configService the SDN-IP configuration service
+     * @param interfaceService the interface service
      */
-    IntentSynchronizer(ApplicationId appId, IntentService intentService) {
+    IntentSynchronizer(ApplicationId appId, IntentService intentService,
+                       SdnIpConfigurationService configService,
+                       InterfaceService interfaceService) {
         this.appId = appId;
         this.intentService = intentService;
         peerIntents = new ConcurrentHashMap<>();
         routeIntents = new ConcurrentHashMap<>();
 
+        this.configService = configService;
+        this.interfaceService = interfaceService;
+
         bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
                 new ThreadFactoryBuilder()
                 .setNameFormat("sdnip-intents-synchronizer-%d").build());
@@ -244,16 +267,85 @@
     }
 
     /**
-     * Updates multi-point-to-single-point route intents.
+     * Generates a route intent for a prefix, the next hop IP address, and
+     * the next hop MAC address.
+     * <p/>
+     * This method will find the egress interface for the intent.
+     * Intent will match dst IP prefix and rewrite dst MAC address at all other
+     * border switches, then forward packets according to dst MAC address.
      *
-     * @param submitIntents the intents to submit
-     * @param withdrawPrefixes the IPv4 or IPv6 matching prefixes for the
-     * intents to withdraw
+     * @param prefix            IP prefix of the route to add
+     * @param nextHopIpAddress  IP address of the next hop
+     * @param nextHopMacAddress MAC address of the next hop
+     * @return the generated intent, or null if no intent should be submitted
      */
-    void updateRouteIntents(
-                Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>> submitIntents,
-                Collection<IpPrefix> withdrawPrefixes) {
+    private MultiPointToSinglePointIntent generateRouteIntent(
+            IpPrefix prefix,
+            IpAddress nextHopIpAddress,
+            MacAddress nextHopMacAddress) {
 
+        // Find the attachment point (egress interface) of the next hop
+        Interface egressInterface;
+        if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
+            // Route to a peer
+            log.debug("Route to peer {}", nextHopIpAddress);
+            BgpPeer peer =
+                    configService.getBgpPeers().get(nextHopIpAddress);
+            egressInterface =
+                    interfaceService.getInterface(peer.connectPoint());
+        } else {
+            // Route to non-peer
+            log.debug("Route to non-peer {}", nextHopIpAddress);
+            egressInterface =
+                    interfaceService.getMatchingInterface(nextHopIpAddress);
+            if (egressInterface == null) {
+                log.warn("No outgoing interface found for {}",
+                         nextHopIpAddress);
+                return null;
+            }
+        }
+
+        //
+        // Generate the intent itself
+        //
+        Set<ConnectPoint> ingressPorts = new HashSet<>();
+        ConnectPoint egressPort = egressInterface.connectPoint();
+        log.debug("Generating intent for prefix {}, next hop mac {}",
+                  prefix, nextHopMacAddress);
+
+        for (Interface intf : interfaceService.getInterfaces()) {
+            if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
+                ConnectPoint srcPort = intf.connectPoint();
+                ingressPorts.add(srcPort);
+            }
+        }
+
+        // Match the destination IP prefix at the first hop
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        if (prefix.version() == Ip4Address.VERSION) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+        }
+        selector.matchIPDst(prefix);
+
+        // Rewrite the destination MAC address
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(nextHopMacAddress);
+        if (!egressInterface.vlan().equals(VlanId.NONE)) {
+            treatment.setVlanId(egressInterface.vlan());
+            // If we set VLAN ID, we have to make sure a VLAN tag exists.
+            // TODO support no VLAN -> VLAN routing
+            selector.matchVlanId(VlanId.ANY);
+        }
+
+        return new MultiPointToSinglePointIntent(appId, selector.build(),
+                                                 treatment.build(),
+                                                 ingressPorts, egressPort);
+    }
+
+    @Override
+    public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
         //
         // NOTE: Semantically, we MUST withdraw existing intents before
         // submitting new intents.
@@ -262,14 +354,19 @@
             MultiPointToSinglePointIntent intent;
 
             log.debug("SDN-IP submitting intents = {} withdrawing = {}",
-                     submitIntents.size(), withdrawPrefixes.size());
+                     updates.size(), withdraws.size());
 
             //
             // Prepare the Intent batch operations for the intents to withdraw
             //
             IntentOperations.Builder withdrawBuilder =
                 IntentOperations.builder(appId);
-            for (IpPrefix prefix : withdrawPrefixes) {
+            for (FibUpdate withdraw : withdraws) {
+                checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
+                              "FibUpdate with wrong type in withdraws list");
+
+                IpPrefix prefix = withdraw.entry().prefix();
+
                 intent = routeIntents.remove(prefix);
                 if (intent == null) {
                     log.trace("SDN-IP No intent in routeIntents to delete " +
@@ -287,10 +384,21 @@
             //
             IntentOperations.Builder submitBuilder =
                 IntentOperations.builder(appId);
-            for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
-                     submitIntents) {
-                IpPrefix prefix = pair.getLeft();
-                intent = pair.getRight();
+            for (FibUpdate update : updates) {
+                checkArgument(update.type() == FibUpdate.Type.UPDATE,
+                              "FibUpdate with wrong type in updates list");
+
+                IpPrefix prefix = update.entry().prefix();
+                intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
+                                             update.entry().nextHopMac());
+
+                if (intent == null) {
+                    // This preserves the old semantics - if an intent can't be
+                    // generated, we don't do anything with that prefix. But
+                    // perhaps we should withdraw the old intent anyway?
+                    continue;
+                }
+
                 MultiPointToSinglePointIntent oldIntent =
                     routeIntents.put(prefix, intent);
                 if (isElectedLeader && isActivatedLeader) {
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/Router.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/Router.java
index 2b2213a..d19f298 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/Router.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/Router.java
@@ -15,8 +15,27 @@
  */
 package org.onosproject.sdnip;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.googlecode.concurrenttrees.common.KeyValuePair;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -28,48 +47,16 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 
-import org.apache.commons.lang3.tuple.Pair;
-import org.onlab.packet.Ethernet;
-import org.onlab.packet.Ip4Address;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
-import org.onosproject.core.ApplicationId;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.Host;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.host.HostEvent;
-import org.onosproject.net.host.HostListener;
-import org.onosproject.net.host.HostService;
-import org.onosproject.net.intent.MultiPointToSinglePointIntent;
-import org.onosproject.sdnip.config.BgpPeer;
-import org.onosproject.sdnip.config.Interface;
-import org.onosproject.sdnip.config.SdnIpConfigurationService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.SetMultimap;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import com.googlecode.concurrenttrees.common.KeyValuePair;
-import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
-import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
-import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
-
 /**
- * This class processes BGP route update, translates each update into a intent
- * and submits the intent.
+ * This class processes route updates and maintains a Routing Information Base
+ * (RIB). After route updates have been processed and next hops have been
+ * resolved, FIB updates are sent to any listening FIB components.
  */
 public class Router implements RouteListener {
 
     private static final Logger log = LoggerFactory.getLogger(Router.class);
 
-    // Store all route updates in a radix tree.
+    // Route entries are stored in a radix tree.
     // The key in this tree is the binary string of prefix of the route.
     private InvertedRadixTree<RouteEntry> ribTable4;
     private InvertedRadixTree<RouteEntry> ribTable6;
@@ -77,37 +64,26 @@
     // Stores all incoming route updates in a queue.
     private final BlockingQueue<Collection<RouteUpdate>> routeUpdatesQueue;
 
-    // The IpAddress is the next hop address of each route update.
+    // Next-hop IP address to route entry mapping for next hops pending MAC resolution
     private final SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
 
     // The IPv4 address to MAC address mapping
     private final Map<IpAddress, MacAddress> ip2Mac;
 
-    private final ApplicationId appId;
-    private final IntentSynchronizer intentSynchronizer;
+    private final FibListener fibComponent;
     private final HostService hostService;
-    private final SdnIpConfigurationService configService;
-    private final InterfaceService interfaceService;
     private final ExecutorService bgpUpdatesExecutor;
     private final HostListener hostListener;
 
     /**
      * Class constructor.
      *
-     * @param appId             the application ID
-     * @param intentSynchronizer the intent synchronizer
-     * @param configService     the configuration service
-     * @param interfaceService  the interface service
+     * @param fibComponent the intent synchronizer
      * @param hostService       the host service
      */
-    public Router(ApplicationId appId, IntentSynchronizer intentSynchronizer,
-                  SdnIpConfigurationService configService,
-                  InterfaceService interfaceService,
-                  HostService hostService) {
-        this.appId = appId;
-        this.intentSynchronizer = intentSynchronizer;
-        this.configService = configService;
-        this.interfaceService = interfaceService;
+    public Router(FibListener fibComponent, HostService hostService) {
+        // TODO move to a listener model for adding fib listeners
+        this.fibComponent = fibComponent;
         this.hostService = hostService;
 
         this.hostListener = new InternalHostListener();
@@ -291,23 +267,23 @@
      */
     void processRouteUpdates(Collection<RouteUpdate> routeUpdates) {
         synchronized (this) {
-            Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
-                submitIntents = new LinkedList<>();
             Collection<IpPrefix> withdrawPrefixes = new LinkedList<>();
-            MultiPointToSinglePointIntent intent;
+            Collection<FibUpdate> fibUpdates = new LinkedList<>();
+            Collection<FibUpdate> fibWithdraws = new LinkedList<>();
 
             for (RouteUpdate update : routeUpdates) {
                 switch (update.type()) {
                 case UPDATE:
-                    intent = processRouteAdd(update.routeEntry(),
-                                             withdrawPrefixes);
-                    if (intent != null) {
-                        submitIntents.add(Pair.of(update.routeEntry().prefix(),
-                                                  intent));
+                    FibEntry fib = processRouteAdd(update.routeEntry(),
+                                                    withdrawPrefixes);
+                    if (fib != null) {
+                        fibUpdates.add(new FibUpdate(FibUpdate.Type.UPDATE, fib));
                     }
+
                     break;
                 case DELETE:
                     processRouteDelete(update.routeEntry(), withdrawPrefixes);
+
                     break;
                 default:
                     log.error("Unknown update Type: {}", update.type());
@@ -315,8 +291,10 @@
                 }
             }
 
-            intentSynchronizer.updateRouteIntents(submitIntents,
-                                                  withdrawPrefixes);
+            withdrawPrefixes.forEach(p -> fibWithdraws.add(new FibUpdate(
+                    FibUpdate.Type.DELETE, new FibEntry(p, null, null))));
+
+            fibComponent.update(fibUpdates, fibWithdraws);
         }
     }
 
@@ -335,9 +313,9 @@
      * @param routeEntry the route entry to add
      * @param withdrawPrefixes the collection of accumulated prefixes whose
      * intents will be withdrawn
-     * @return the corresponding intent that should be submitted, or null
+     * @return the corresponding FIB entry change, or null
      */
-    private MultiPointToSinglePointIntent processRouteAdd(
+    private FibEntry processRouteAdd(
                 RouteEntry routeEntry,
                 Collection<IpPrefix> withdrawPrefixes) {
         log.debug("Processing route add: {}", routeEntry);
@@ -397,86 +375,8 @@
             routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
             return null;
         }
-        return generateRouteIntent(routeEntry.prefix(), routeEntry.nextHop(),
-                                   nextHopMacAddress);
-    }
-
-    /**
-     * Generates a route intent for a prefix, the next hop IP address, and
-     * the next hop MAC address.
-     * <p/>
-     * This method will find the egress interface for the intent.
-     * Intent will match dst IP prefix and rewrite dst MAC address at all other
-     * border switches, then forward packets according to dst MAC address.
-     *
-     * @param prefix            IP prefix of the route to add
-     * @param nextHopIpAddress  IP address of the next hop
-     * @param nextHopMacAddress MAC address of the next hop
-     * @return the generated intent, or null if no intent should be submitted
-     */
-    private MultiPointToSinglePointIntent generateRouteIntent(
-                IpPrefix prefix,
-                IpAddress nextHopIpAddress,
-                MacAddress nextHopMacAddress) {
-
-        // Find the attachment point (egress interface) of the next hop
-        Interface egressInterface;
-        if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
-            // Route to a peer
-            log.debug("Route to peer {}", nextHopIpAddress);
-            BgpPeer peer =
-                    configService.getBgpPeers().get(nextHopIpAddress);
-            egressInterface =
-                    interfaceService.getInterface(peer.connectPoint());
-        } else {
-            // Route to non-peer
-            log.debug("Route to non-peer {}", nextHopIpAddress);
-            egressInterface =
-                    interfaceService.getMatchingInterface(nextHopIpAddress);
-            if (egressInterface == null) {
-                log.warn("No outgoing interface found for {}",
-                         nextHopIpAddress);
-                return null;
-            }
-        }
-
-        //
-        // Generate the intent itself
-        //
-        Set<ConnectPoint> ingressPorts = new HashSet<>();
-        ConnectPoint egressPort = egressInterface.connectPoint();
-        log.debug("Generating intent for prefix {}, next hop mac {}",
-                  prefix, nextHopMacAddress);
-
-        for (Interface intf : interfaceService.getInterfaces()) {
-            if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
-                ConnectPoint srcPort = intf.connectPoint();
-                ingressPorts.add(srcPort);
-            }
-        }
-
-        // Match the destination IP prefix at the first hop
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        if (prefix.version() == Ip4Address.VERSION) {
-            selector.matchEthType(Ethernet.TYPE_IPV4);
-        } else {
-            selector.matchEthType(Ethernet.TYPE_IPV6);
-        }
-        selector.matchIPDst(prefix);
-
-        // Rewrite the destination MAC address
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
-                .setEthDst(nextHopMacAddress);
-        if (!egressInterface.vlan().equals(VlanId.NONE)) {
-            treatment.setVlanId(egressInterface.vlan());
-            // If we set VLAN ID, we have to make sure a VLAN tag exists.
-            // TODO support no VLAN -> VLAN routing
-            selector.matchVlanId(VlanId.ANY);
-        }
-
-        return new MultiPointToSinglePointIntent(appId, selector.build(),
-                                                 treatment.build(),
-                                                 ingressPorts, egressPort);
+        return new FibEntry(routeEntry.prefix(), routeEntry.nextHop(),
+                             nextHopMacAddress);
     }
 
     /**
@@ -528,9 +428,7 @@
         // tree and the intents could get out of sync.
         //
         synchronized (this) {
-            Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
-                submitIntents = new LinkedList<>();
-            MultiPointToSinglePointIntent intent;
+            Collection<FibUpdate> submitFibEntries = new LinkedList<>();
 
             Set<RouteEntry> routesToPush =
                 routesWaitingOnArp.removeAll(ipAddress);
@@ -540,27 +438,21 @@
                 RouteEntry foundRouteEntry = findRibRoute(routeEntry.prefix());
                 if (foundRouteEntry != null &&
                     foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
-                    // We only push prefix flows if the prefix is still in the
-                    // radix tree and the next hop is the same as our
-                    // update.
+                    // We only push FIB updates if the prefix is still in the
+                    // radix tree and the next hop is the same as our entry.
                     // The prefix could have been removed while we were waiting
                     // for the ARP, or the next hop could have changed.
-                    intent = generateRouteIntent(routeEntry.prefix(),
-                                                 ipAddress, macAddress);
-                    if (intent != null) {
-                        submitIntents.add(Pair.of(routeEntry.prefix(),
-                                                  intent));
-                    }
+                    submitFibEntries.add(new FibUpdate(FibUpdate.Type.UPDATE,
+                                                       new FibEntry(routeEntry.prefix(),
+                                                       ipAddress, macAddress)));
                 } else {
                     log.debug("{} has been revoked before the MAC was resolved",
                               routeEntry);
                 }
             }
 
-            if (!submitIntents.isEmpty()) {
-                Collection<IpPrefix> withdrawPrefixes = new LinkedList<>();
-                intentSynchronizer.updateRouteIntents(submitIntents,
-                                                      withdrawPrefixes);
+            if (!submitFibEntries.isEmpty()) {
+                fibComponent.update(submitFibEntries, Collections.emptyList());
             }
 
             ip2Mac.put(ipAddress, macAddress);
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java
index a910950..0b921ff 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIp.java
@@ -15,11 +15,6 @@
  */
 package org.onosproject.sdnip;
 
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Collection;
-import java.util.Dictionary;
-
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -44,6 +39,11 @@
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
+import java.util.Collection;
+import java.util.Dictionary;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * Component for the SDN-IP peering application.
  */
@@ -104,7 +104,8 @@
         InterfaceService interfaceService =
             new HostToInterfaceAdaptor(hostService);
 
-        intentSynchronizer = new IntentSynchronizer(appId, intentService);
+        intentSynchronizer = new IntentSynchronizer(appId, intentService,
+                                                    config, interfaceService);
         intentSynchronizer.start();
 
         peerConnectivity = new PeerConnectivityManager(appId,
@@ -113,8 +114,7 @@
                                                        interfaceService);
         peerConnectivity.start();
 
-        router = new Router(appId, intentSynchronizer, config,
-                            interfaceService, hostService);
+        router = new Router(intentSynchronizer, hostService);
         router.start();
 
         leadershipService.addListener(leadershipEventListener);
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java
index 9960481..e4808f7 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/IntentSyncTest.java
@@ -15,23 +15,10 @@
  */
 package org.onosproject.sdnip;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
+import com.google.common.collect.Sets;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.junit.TestUtils;
@@ -67,10 +54,16 @@
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.sdnip.config.Interface;
 
-import com.google.common.collect.Sets;
-import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
-import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
-import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 /**
  * This class tests the intent synchronization function in the
@@ -116,9 +109,9 @@
         setUpHostService();
         intentService = createMock(IntentService.class);
 
-        intentSynchronizer = new IntentSynchronizer(APPID, intentService);
-        router = new Router(APPID, intentSynchronizer, null, interfaceService,
-                            hostService);
+        intentSynchronizer = new IntentSynchronizer(APPID, intentService,
+                                                    null, interfaceService);
+        router = new Router(intentSynchronizer, hostService);
     }
 
     /**
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
index e542eb0..5311a72 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
@@ -15,19 +15,7 @@
  */
 package org.onosproject.sdnip;
 
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
+import com.google.common.collect.Sets;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -60,7 +48,14 @@
 import org.onosproject.sdnip.config.InterfaceAddress;
 import org.onosproject.sdnip.config.SdnIpConfigurationService;
 
-import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static org.easymock.EasyMock.*;
 
 /**
  * Unit tests for PeerConnectivityManager.
@@ -552,7 +547,9 @@
         intentService = createMock(IntentService.class);
         replay(intentService);
 
-        intentSynchronizer = new IntentSynchronizer(APPID, intentService);
+        intentSynchronizer = new IntentSynchronizer(APPID, intentService,
+                                                    configInfoService,
+                                                    interfaceService);
         intentSynchronizer.leaderChanged(true);
         TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
 
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/RouterAsyncArpTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/RouterAsyncArpTest.java
index 80fb578..4d14228 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/RouterAsyncArpTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/RouterAsyncArpTest.java
@@ -15,22 +15,10 @@
  */
 package org.onosproject.sdnip;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
+import com.google.common.collect.Sets;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.junit.TestUtils;
@@ -69,10 +57,16 @@
 import org.onosproject.sdnip.config.Interface;
 import org.onosproject.sdnip.config.SdnIpConfigurationService;
 
-import com.google.common.collect.Sets;
-import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
-import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
-import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * This class tests adding a route, updating a route, deleting a route, and
@@ -122,9 +116,10 @@
         hostService = createMock(HostService.class);
         intentService = createMock(IntentService.class);
 
-        intentSynchronizer = new IntentSynchronizer(APPID, intentService);
-        router = new Router(APPID, intentSynchronizer,
-                            sdnIpConfigService, interfaceService, hostService);
+        intentSynchronizer = new IntentSynchronizer(APPID, intentService,
+                                                    sdnIpConfigService,
+                                                    interfaceService);
+        router = new Router(intentSynchronizer, hostService);
         internalHostListener = router.new InternalHostListener();
     }
 
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/RouterTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/RouterTest.java
index 6c98efb..a7b4cce 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/RouterTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/RouterTest.java
@@ -15,22 +15,7 @@
  */
 package org.onosproject.sdnip;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
+import com.google.common.collect.Sets;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.junit.TestUtils;
@@ -68,7 +53,15 @@
 import org.onosproject.sdnip.config.Interface;
 import org.onosproject.sdnip.config.SdnIpConfigurationService;
 
-import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * This class tests adding a route, updating a route, deleting a route,
@@ -125,9 +118,10 @@
 
         intentService = createMock(IntentService.class);
 
-        intentSynchronizer = new IntentSynchronizer(APPID, intentService);
-        router = new Router(APPID, intentSynchronizer, sdnIpConfigService,
-                            interfaceService, hostService);
+        intentSynchronizer = new IntentSynchronizer(APPID, intentService,
+                                                    sdnIpConfigService,
+                                                    interfaceService);
+        router = new Router(intentSynchronizer, hostService);
     }
 
     /**
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpTest.java
index 36bd3b7..157c73f 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpTest.java
@@ -15,26 +15,7 @@
  */
 package org.onosproject.sdnip;
 
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
+import com.google.common.collect.Sets;
 import org.easymock.IAnswer;
 import org.junit.Before;
 import org.junit.Test;
@@ -66,7 +47,20 @@
 import org.onosproject.sdnip.config.Interface;
 import org.onosproject.sdnip.config.SdnIpConfigurationService;
 
-import com.google.common.collect.Sets;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 /**
  * Integration tests for the SDN-IP application.
@@ -130,9 +124,10 @@
         intentService = createMock(IntentService.class);
         random = new Random();
 
-        intentSynchronizer = new IntentSynchronizer(APPID, intentService);
-        router = new Router(APPID, intentSynchronizer, sdnIpConfigService,
-                            interfaceService, hostService);
+        intentSynchronizer = new IntentSynchronizer(APPID, intentService,
+                                                    sdnIpConfigService,
+                                                    interfaceService);
+        router = new Router(intentSynchronizer, hostService);
     }
 
     /**