Make vRouter components into separate apps.

This allows us to leverage the ONOS app subsystem for selecting which
components to load.

CORD-710

Change-Id: Ibd7c4c1afd2caa137b44c085e7b6b5b4a1082521
diff --git a/apps/routing/cpr/BUCK b/apps/routing/cpr/BUCK
new file mode 100644
index 0000000..89a499d
--- /dev/null
+++ b/apps/routing/cpr/BUCK
@@ -0,0 +1,30 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//incubator/api:onos-incubator-api',
+    '//apps/routing-api:onos-apps-routing-api',
+]
+
+TEST_DEPS = [
+    '//lib:TEST_ADAPTERS',
+    '//incubator/api:onos-incubator-api-tests',
+    '//apps/routing-api:onos-apps-routing-api-tests',
+]
+
+osgi_jar_with_tests (
+    deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
+)
+
+BUNDLES = [
+    '//apps/routing/cpr:onos-apps-routing-cpr',
+    '//apps/routing-api:onos-apps-routing-api',
+]
+
+onos_app (
+    app_name = 'org.onosproject.cpr',
+    title = 'Control plane redirect',
+    category = 'Traffic Steering',
+    url = 'http://onosproject.org',
+    description = 'Redirects routing control traffic to a control plane',
+    included_bundles = BUNDLES,
+)
diff --git a/apps/routing/cpr/pom.xml b/apps/routing/cpr/pom.xml
new file mode 100644
index 0000000..cbc03df
--- /dev/null
+++ b/apps/routing/cpr/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2017-present 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>onos-app-routing-parent</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.9.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>onos-apps-routing-cpr</artifactId>
+    <packaging>bundle</packaging>
+
+
+</project>
diff --git a/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/ControlPlaneRedirectManager.java b/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/ControlPlaneRedirectManager.java
new file mode 100644
index 0000000..e1d2d39
--- /dev/null
+++ b/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/ControlPlaneRedirectManager.java
@@ -0,0 +1,724 @@
+/*
+ * Copyright 2017-present 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.routing.cpr;
+
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.device.DeviceService;
+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.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.routing.AsyncDeviceFetcher;
+import org.onosproject.routing.RouterInterfaceManager;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.RouterConfig;
+import org.slf4j.Logger;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.onlab.packet.Ethernet.TYPE_ARP;
+import static org.onlab.packet.Ethernet.TYPE_IPV4;
+import static org.onlab.packet.Ethernet.TYPE_IPV6;
+import static org.onlab.packet.ICMP6.NEIGHBOR_ADVERTISEMENT;
+import static org.onlab.packet.ICMP6.NEIGHBOR_SOLICITATION;
+import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages connectivity between peers redirecting control traffic to a routing
+ * control plane available on the dataplane.
+ */
+@Component(immediate = true)
+public class ControlPlaneRedirectManager {
+
+    private final Logger log = getLogger(getClass());
+
+    public static final short ASSIGNED_VLAN = 4094;
+
+    private static final int MIN_IP_PRIORITY = 10;
+    private static final int IPV4_PRIORITY = 2000;
+    private static final int IPV6_PRIORITY = 500;
+    static final int ACL_PRIORITY = 40001;
+    private static final int OSPF_IP_PROTO = 0x59;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigService networkConfigService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ApplicationService applicationService;
+
+    private static final String APP_NAME = "org.onosproject.vrouter";
+    private ApplicationId appId;
+
+    private ConnectPoint controlPlaneConnectPoint;
+    private boolean ospfEnabled = false;
+    private Map<Host, Set<Integer>> peerNextId = Maps.newConcurrentMap();
+
+    private RouterInterfaceManager interfaceManager;
+    private AsyncDeviceFetcher asyncDeviceFetcher;
+
+    private final InternalNetworkConfigListener networkConfigListener =
+            new InternalNetworkConfigListener();
+    private final InternalHostListener hostListener = new InternalHostListener();
+
+    @Activate
+    public void activate() {
+        this.appId = coreService.registerApplication(APP_NAME);
+
+        networkConfigService.addListener(networkConfigListener);
+        hostService.addListener(hostListener);
+
+        asyncDeviceFetcher = AsyncDeviceFetcher.create(deviceService);
+
+        processRouterConfig();
+
+
+        // FIXME There can be an issue when this component is deactivated before vRouter
+        applicationService.registerDeactivateHook(this.appId, () -> {
+            if (interfaceManager != null) {
+                interfaceManager.cleanup();
+            }
+        });
+    }
+
+    @Deactivate
+    public void deactivate() {
+        networkConfigService.removeListener(networkConfigListener);
+        hostService.removeListener(hostListener);
+        asyncDeviceFetcher.shutdown();
+    }
+
+    /**
+     * Sets up the router interfaces if router config is available.
+     */
+    private void processRouterConfig() {
+        ApplicationId routingAppId =
+                coreService.registerApplication(RoutingService.ROUTER_APP_ID);
+
+        RouterConfig config = networkConfigService.getConfig(
+                routingAppId, RoutingService.ROUTER_CONFIG_CLASS);
+
+        if (config == null) {
+            log.warn("Router config not available");
+            return;
+        }
+
+        if (interfaceManager == null) {
+            controlPlaneConnectPoint = config.getControlPlaneConnectPoint();
+            ospfEnabled = config.getOspfEnabled();
+
+            DeviceId deviceId = config.getControlPlaneConnectPoint().deviceId();
+
+            asyncDeviceFetcher.getDevice(deviceId)
+                    .thenAccept(deviceId1 ->
+                            interfaceManager = createRouter(deviceId,
+                                    Sets.newHashSet(config.getInterfaces())));
+
+        } else {
+            interfaceManager.changeConfiguredInterfaces(Sets.newHashSet(config.getInterfaces()));
+        }
+    }
+
+    /**
+     * Cleans up after router config was removed.
+     */
+    private void removeRouterConfig() {
+        if (interfaceManager != null) {
+            interfaceManager.cleanup();
+        }
+    }
+
+    private RouterInterfaceManager createRouter(DeviceId deviceId, Set<String> configuredInterfaces) {
+        return new RouterInterfaceManager(deviceId,
+                configuredInterfaces,
+                interfaceService,
+                this::provisionInterface,
+                this::unprovisionInterface);
+    }
+
+    private void provisionInterface(Interface intf) {
+        updateInterfaceObjectives(intf, true);
+    }
+
+    private void unprovisionInterface(Interface intf) {
+        updateInterfaceObjectives(intf, false);
+    }
+
+    /**
+     * Installs or removes flow objectives relating to a give interface.
+     *
+     * @param intf interface to change objectives for
+     * @param install true to install the objectives, false to remove them
+     */
+    private void updateInterfaceObjectives(Interface intf, boolean install) {
+        updateInterfaceForwarding(intf, install);
+        updateOspfForwarding(intf, install);
+    }
+
+    /**
+     * Installs or removes the basic forwarding flows for each interface.
+     *
+     * @param intf the Interface on which event is received
+     * @param install true to install the objectives, false to remove them
+     */
+    private void updateInterfaceForwarding(Interface intf, boolean install) {
+        log.debug("{} interface objectives for {}", operation(install), intf);
+
+        DeviceId deviceId = intf.connectPoint().deviceId();
+        PortNumber controlPlanePort = controlPlaneConnectPoint.port();
+        for (InterfaceIpAddress ip : intf.ipAddresses()) {
+            // create nextObjectives for forwarding to this interface and the
+            // controlPlaneConnectPoint
+            int cpNextId, intfNextId;
+            if (intf.vlan() == VlanId.NONE) {
+                cpNextId = modifyNextObjective(deviceId, controlPlanePort,
+                               VlanId.vlanId(ASSIGNED_VLAN),
+                               true, install);
+                intfNextId = modifyNextObjective(deviceId, intf.connectPoint().port(),
+                               VlanId.vlanId(ASSIGNED_VLAN),
+                               true, install);
+            } else {
+                cpNextId = modifyNextObjective(deviceId, controlPlanePort,
+                                               intf.vlan(), false, install);
+                intfNextId = modifyNextObjective(deviceId, intf.connectPoint().port(),
+                                                 intf.vlan(), false, install);
+            }
+            List<ForwardingObjective> fwdToSend = Lists.newArrayList();
+            TrafficSelector selector;
+            // IP traffic toward the router.
+            selector = buildIPDstSelector(
+                    ip.ipAddress().toIpPrefix(),
+                    intf.connectPoint().port(),
+                    null,
+                    intf.mac(),
+                    intf.vlan()
+            );
+            fwdToSend.add(buildForwardingObjective(selector, null, cpNextId, install, ACL_PRIORITY));
+            // IP traffic from the router.
+            selector = buildIPSrcSelector(
+                    ip.ipAddress().toIpPrefix(),
+                    controlPlanePort,
+                    intf.mac(),
+                    null,
+                    intf.vlan()
+            );
+            fwdToSend.add(buildForwardingObjective(selector, null, intfNextId, install, ACL_PRIORITY));
+            // We build the punt treatment.
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .punt()
+                    .build();
+            // Handling of neighbour discovery protocols.
+            // IPv4 traffic - we have to deal with the ARP protocol.
+            // IPv6 traffic - we have to deal with the NDP protocol.
+            if (ip.ipAddress().isIp4()) {
+                 // ARP traffic towards the router.
+                selector = buildArpSelector(
+                        intf.connectPoint().port(),
+                        intf.vlan(),
+                        null,
+                        null
+                );
+                fwdToSend.add(buildForwardingObjective(selector, treatment, cpNextId, install, ACL_PRIORITY + 1));
+                // ARP traffic from the router.
+                selector = buildArpSelector(
+                        controlPlanePort,
+                        intf.vlan(),
+                        ip.ipAddress().getIp4Address(),
+                        intf.mac()
+                );
+                fwdToSend.add(buildForwardingObjective(selector, treatment, intfNextId, install, ACL_PRIORITY + 1));
+            } else {
+                // Neighbour solicitation traffic towards the router.
+                selector = buildNdpSelector(
+                        intf.connectPoint().port(),
+                        intf.vlan(),
+                        null,
+                        NEIGHBOR_SOLICITATION,
+                        null
+                );
+                fwdToSend.add(buildForwardingObjective(selector, treatment, cpNextId, install, ACL_PRIORITY + 1));
+                // Neighbour solicitation traffic from the router.
+                selector = buildNdpSelector(
+                        controlPlanePort,
+                        intf.vlan(),
+                        ip.ipAddress().toIpPrefix(),
+                        NEIGHBOR_SOLICITATION,
+                        intf.mac()
+                );
+                fwdToSend.add(buildForwardingObjective(selector, treatment, intfNextId, install, ACL_PRIORITY + 1));
+                 // Neighbour advertisement traffic towards the router.
+                selector = buildNdpSelector(
+                        intf.connectPoint().port(),
+                        intf.vlan(),
+                        null,
+                        NEIGHBOR_ADVERTISEMENT,
+                        null
+                );
+                fwdToSend.add(buildForwardingObjective(selector, treatment, cpNextId, install, ACL_PRIORITY + 1));
+                // Neighbour advertisement traffic from the router.
+                selector = buildNdpSelector(
+                        controlPlanePort,
+                        intf.vlan(),
+                        ip.ipAddress().toIpPrefix(),
+                        NEIGHBOR_ADVERTISEMENT,
+                        intf.mac()
+                );
+                fwdToSend.add(buildForwardingObjective(selector, treatment, intfNextId, install, ACL_PRIORITY + 1));
+            }
+            // Finally we push the fwd objectives through the flow objective service.
+            fwdToSend.stream().forEach(forwardingObjective ->
+                flowObjectiveService.forward(deviceId, forwardingObjective)
+            );
+        }
+    }
+
+    /**
+     * Installs or removes OSPF forwarding rules.
+     *
+     * @param intf the interface on which event is received
+     * @param install true to create an add objective, false to create a remove
+     *            objective
+     */
+    private void updateOspfForwarding(Interface intf, boolean install) {
+        // TODO IPv6 support has not been implemented yet
+
+        log.debug("{} OSPF flows for {}", operation(install), intf);
+
+        // OSPF to router
+        TrafficSelector toSelector = DefaultTrafficSelector.builder()
+                .matchInPort(intf.connectPoint().port())
+                .matchEthType(EthType.EtherType.IPV4.ethType().toShort())
+                .matchVlanId(intf.vlan())
+                .matchIPProtocol((byte) OSPF_IP_PROTO)
+                .build();
+
+        // create nextObjectives for forwarding to the controlPlaneConnectPoint
+        DeviceId deviceId = intf.connectPoint().deviceId();
+        PortNumber controlPlanePort = intf.connectPoint().port();
+        int cpNextId;
+        if (intf.vlan() == VlanId.NONE) {
+            cpNextId = modifyNextObjective(deviceId, controlPlanePort,
+                           VlanId.vlanId(ASSIGNED_VLAN),
+                           true, install);
+        } else {
+            cpNextId = modifyNextObjective(deviceId, controlPlanePort,
+                                           intf.vlan(), false, install);
+        }
+        flowObjectiveService.forward(intf.connectPoint().deviceId(),
+                buildForwardingObjective(toSelector, null, cpNextId,
+                        install ? ospfEnabled : install, ACL_PRIORITY));
+    }
+
+    /**
+     * Creates a next objective for forwarding to a port. Handles metadata for
+     * some pipelines that require vlan information for egress port.
+     *
+     * @param deviceId the device on which the next objective is being created
+     * @param portNumber the egress port
+     * @param vlanId vlan information for egress port
+     * @param popVlan if vlan tag should be popped or not
+     * @param install true to create an add next objective, false to create a remove
+     *            next objective
+     * @return nextId of the next objective created
+     */
+    private int modifyNextObjective(DeviceId deviceId, PortNumber portNumber,
+                                    VlanId vlanId, boolean popVlan, boolean install) {
+        int nextId = flowObjectiveService.allocateNextId();
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.SIMPLE)
+                .fromApp(appId);
+
+        TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
+        if (popVlan) {
+            ttBuilder.popVlan();
+        }
+        ttBuilder.setOutput(portNumber);
+
+        // setup metadata to pass to nextObjective - indicate the vlan on egress
+        // if needed by the switch pipeline.
+        TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
+        metabuilder.matchVlanId(vlanId);
+
+        nextObjBuilder.withMeta(metabuilder.build());
+        nextObjBuilder.addTreatment(ttBuilder.build());
+        log.debug("Submitted next objective {} in device {} for port/vlan {}/{}",
+                nextId, deviceId, portNumber, vlanId);
+        if (install) {
+             flowObjectiveService.next(deviceId, nextObjBuilder.add());
+        } else {
+             flowObjectiveService.next(deviceId, nextObjBuilder.remove());
+        }
+        return nextId;
+    }
+
+    /**
+     * Builds a forwarding objective from the given selector, treatment and nextId.
+     *
+     * @param selector selector
+     * @param treatment treatment to apply to packet, can be null
+     * @param nextId next objective to point to for forwarding packet
+     * @param add true to create an add objective, false to create a remove
+     *            objective
+     * @return forwarding objective
+     */
+    private ForwardingObjective buildForwardingObjective(TrafficSelector selector,
+                                                         TrafficTreatment treatment,
+                                                         int nextId,
+                                                         boolean add,
+                                                         int priority) {
+        DefaultForwardingObjective.Builder fobBuilder = DefaultForwardingObjective.builder();
+        fobBuilder.withSelector(selector);
+        if (treatment != null) {
+            fobBuilder.withTreatment(treatment);
+        }
+        if (nextId != -1) {
+            fobBuilder.nextStep(nextId);
+        }
+        fobBuilder.fromApp(appId)
+            .withPriority(priority)
+            .withFlag(ForwardingObjective.Flag.VERSATILE);
+
+        return add ? fobBuilder.add() : fobBuilder.remove();
+    }
+
+
+    static TrafficSelector.Builder buildBaseSelectorBuilder(PortNumber inPort,
+                                                            MacAddress srcMac,
+                                                            MacAddress dstMac,
+                                                            VlanId vlanId) {
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+        if (inPort != null) {
+            selectorBuilder.matchInPort(inPort);
+        }
+        if (srcMac != null) {
+            selectorBuilder.matchEthSrc(srcMac);
+        }
+        if (dstMac != null) {
+            selectorBuilder.matchEthDst(dstMac);
+        }
+        if (vlanId != null) {
+            selectorBuilder.matchVlanId(vlanId);
+        }
+        return selectorBuilder;
+    }
+
+    static TrafficSelector buildIPDstSelector(IpPrefix dstIp,
+                                              PortNumber inPort,
+                                              MacAddress srcMac,
+                                              MacAddress dstMac,
+                                              VlanId vlanId) {
+        TrafficSelector.Builder selector = buildBaseSelectorBuilder(inPort, srcMac, dstMac, vlanId);
+        if (dstIp.isIp4()) {
+            selector.matchEthType(TYPE_IPV4);
+            selector.matchIPDst(dstIp);
+        } else {
+            selector.matchEthType(TYPE_IPV6);
+            selector.matchIPv6Dst(dstIp);
+        }
+        return selector.build();
+    }
+
+    static TrafficSelector buildIPSrcSelector(IpPrefix srcIp,
+                                              PortNumber inPort,
+                                              MacAddress srcMac,
+                                              MacAddress dstMac,
+                                              VlanId vlanId) {
+        TrafficSelector.Builder selector = buildBaseSelectorBuilder(inPort, srcMac, dstMac, vlanId);
+        if (srcIp.isIp4()) {
+            selector.matchEthType(TYPE_IPV4);
+            selector.matchIPSrc(srcIp);
+        } else {
+            selector.matchEthType(TYPE_IPV6);
+            selector.matchIPv6Src(srcIp);
+        }
+        return selector.build();
+    }
+
+    static TrafficSelector buildArpSelector(PortNumber inPort,
+                                            VlanId vlanId,
+                                            Ip4Address arpSpa,
+                                            MacAddress srcMac) {
+        TrafficSelector.Builder selector = buildBaseSelectorBuilder(inPort, null, null, vlanId);
+        selector.matchEthType(TYPE_ARP);
+        if (arpSpa != null) {
+            selector.matchArpSpa(arpSpa);
+        }
+        if (srcMac != null) {
+            selector.matchEthSrc(srcMac);
+        }
+        return selector.build();
+    }
+
+    static TrafficSelector buildNdpSelector(PortNumber inPort,
+                                            VlanId vlanId,
+                                            IpPrefix srcIp,
+                                            byte subProto,
+                                            MacAddress srcMac) {
+        TrafficSelector.Builder selector = buildBaseSelectorBuilder(inPort, null, null, vlanId);
+        selector.matchEthType(TYPE_IPV6)
+                .matchIPProtocol(PROTOCOL_ICMP6)
+                .matchIcmpv6Type(subProto);
+        if (srcIp != null) {
+            selector.matchIPv6Src(srcIp);
+        }
+        if (srcMac != null) {
+            selector.matchEthSrc(srcMac);
+        }
+        return selector.build();
+    }
+
+    private int getPriorityFromPrefix(IpPrefix prefix) {
+        return (prefix.isIp4()) ?
+               IPV4_PRIORITY * prefix.prefixLength() + MIN_IP_PRIORITY :
+               IPV6_PRIORITY * prefix.prefixLength() + MIN_IP_PRIORITY;
+    }
+
+    private String operation(boolean install) {
+        return install ? "Installing" : "Removing";
+    }
+
+
+    /**
+     * Listener for network config events.
+     */
+    private class InternalNetworkConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            if (event.configClass().equals(RoutingService.ROUTER_CONFIG_CLASS)) {
+                switch (event.type()) {
+                    case CONFIG_ADDED:
+                    case CONFIG_UPDATED:
+                        processRouterConfig();
+                        break;
+                    case CONFIG_REGISTERED:
+                        break;
+                    case CONFIG_UNREGISTERED:
+                        break;
+                    case CONFIG_REMOVED:
+                        removeRouterConfig();
+                        break;
+                default:
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Listener for host events.
+     */
+    private class InternalHostListener implements HostListener {
+
+        private Optional<Interface> getPeerInterface(Host peer) {
+            return interfaceService.getInterfacesByPort(peer.location()).stream()
+                    .filter(intf -> interfaceManager.configuredInterfaces().isEmpty()
+                            || interfaceManager.configuredInterfaces().contains(intf.name()))
+                    .filter(intf -> peer.vlan().equals(intf.vlan()))
+                    .findFirst();
+        }
+
+        private void peerAdded(HostEvent event) {
+            Host peer = event.subject();
+            if (interfaceManager == null) {
+                return;
+            }
+
+            Optional<Interface> peerIntf = getPeerInterface(peer);
+            if (!peerIntf.isPresent()) {
+                log.debug("Adding peer {}/{} on {} but the interface is not configured",
+                        peer.mac(), peer.vlan(), peer.location());
+                return;
+            }
+
+            // Generate L3 Unicast groups and store it in the map
+            int toRouterL3Unicast = createPeerGroup(peer.mac(), peerIntf.get().mac(),
+                    peer.vlan(), peer.location().deviceId(), peerIntf.get().connectPoint().port());
+            int toPeerL3Unicast = createPeerGroup(peerIntf.get().mac(), peer.mac(),
+                    peer.vlan(), peer.location().deviceId(), peer.location().port());
+            peerNextId.put(peer, ImmutableSortedSet.of(toRouterL3Unicast, toPeerL3Unicast));
+
+            // From peer to router
+            peerIntf.get().ipAddresses().forEach(routerIp -> {
+                flowObjectiveService.forward(peer.location().deviceId(),
+                        createPeerObjBuilder(toRouterL3Unicast, routerIp.ipAddress().toIpPrefix()).add());
+            });
+
+            // From router to peer
+            peer.ipAddresses().forEach(peerIp -> {
+                flowObjectiveService.forward(peer.location().deviceId(),
+                        createPeerObjBuilder(toPeerL3Unicast, peerIp.toIpPrefix()).add());
+            });
+        }
+
+        private void peerRemoved(HostEvent event) {
+            Host peer = event.subject();
+            Optional<Interface> peerIntf = getPeerInterface(peer);
+            if (!peerIntf.isPresent()) {
+                log.debug("Removing peer {}/{} on {} but the interface is not configured",
+                        peer.mac(), peer.vlan(), peer.location());
+                return;
+            }
+
+            checkState(peerNextId.get(peer) != null,
+                    "Peer nextId should not be null");
+            checkState(peerNextId.get(peer).size() == 2,
+                    "Wrong nextId associated with the peer");
+            Iterator<Integer> iter = peerNextId.get(peer).iterator();
+            int toRouterL3Unicast = iter.next();
+            int toPeerL3Unicast = iter.next();
+
+            // From peer to router
+            peerIntf.get().ipAddresses().forEach(routerIp -> {
+                flowObjectiveService.forward(peer.location().deviceId(),
+                        createPeerObjBuilder(toRouterL3Unicast, routerIp.ipAddress().toIpPrefix()).remove());
+            });
+
+            // From router to peer
+            peer.ipAddresses().forEach(peerIp -> {
+                flowObjectiveService.forward(peer.location().deviceId(),
+                        createPeerObjBuilder(toPeerL3Unicast, peerIp.toIpPrefix()).remove());
+            });
+        }
+
+        private ForwardingObjective.Builder createPeerObjBuilder(
+                int nextId, IpPrefix ipAddresses) {
+            TrafficSelector selector = buildIPDstSelector(ipAddresses, null, null, null, null);
+            DefaultForwardingObjective.Builder builder =
+                    DefaultForwardingObjective.builder()
+                    .withSelector(selector)
+                    .fromApp(appId)
+                    .withPriority(getPriorityFromPrefix(ipAddresses))
+                    .withFlag(ForwardingObjective.Flag.SPECIFIC);
+            if (nextId != -1) {
+                builder.nextStep(nextId);
+            }
+            return builder;
+        }
+
+        private int createPeerGroup(MacAddress srcMac, MacAddress dstMac,
+                VlanId vlanId, DeviceId deviceId, PortNumber port) {
+            int nextId = flowObjectiveService.allocateNextId();
+            NextObjective.Builder nextObjBuilder = DefaultNextObjective.builder()
+                    .withId(nextId)
+                    .withType(NextObjective.Type.SIMPLE)
+                    .fromApp(appId);
+
+            TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
+            ttBuilder.setEthSrc(srcMac);
+            ttBuilder.setEthDst(dstMac);
+            ttBuilder.setOutput(port);
+            nextObjBuilder.addTreatment(ttBuilder.build());
+
+            TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
+            VlanId matchVlanId = (vlanId.equals(VlanId.NONE)) ?
+                    VlanId.vlanId(ASSIGNED_VLAN) :
+                    vlanId;
+            metabuilder.matchVlanId(matchVlanId);
+            nextObjBuilder.withMeta(metabuilder.build());
+
+            flowObjectiveService.next(deviceId, nextObjBuilder.add());
+            return nextId;
+        }
+
+        @Override
+        public void event(HostEvent event) {
+            DeviceId deviceId = event.subject().location().deviceId();
+            if (!mastershipService.isLocalMaster(deviceId)) {
+                return;
+            }
+            switch (event.type()) {
+                case HOST_ADDED:
+                    peerAdded(event);
+                    break;
+                case HOST_MOVED:
+                    //TODO We assume BGP peer does not move for now
+                    break;
+                case HOST_REMOVED:
+                    peerRemoved(event);
+                    break;
+                case HOST_UPDATED:
+                    //FIXME We assume BGP peer does not change IP for now
+                    // but we can discover new address.
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+}
+
diff --git a/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/package-info.java b/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/package-info.java
new file mode 100644
index 0000000..b098961
--- /dev/null
+++ b/apps/routing/cpr/src/main/java/org/onosproject/routing/cpr/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present 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.
+ */
+
+/**
+ * Control plane redirect.
+ */
+package org.onosproject.routing.cpr;
diff --git a/apps/routing/cpr/src/test/java/org/onosproject/routing/cpr/ControlPlaneRedirectManagerTest.java b/apps/routing/cpr/src/test/java/org/onosproject/routing/cpr/ControlPlaneRedirectManagerTest.java
new file mode 100644
index 0000000..1291f8f
--- /dev/null
+++ b/apps/routing/cpr/src/test/java/org/onosproject/routing/cpr/ControlPlaneRedirectManagerTest.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright 2017-present 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.routing.cpr;
+
+import com.google.common.collect.Sets;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceEvent;
+import org.onosproject.incubator.net.intf.InterfaceListener;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.incubator.net.intf.InterfaceServiceAdapter;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.NetTestTools;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigEvent.Type;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.NetworkConfigServiceAdapter;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+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.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.RouterConfig;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.onlab.packet.ICMP6.NEIGHBOR_ADVERTISEMENT;
+import static org.onlab.packet.ICMP6.NEIGHBOR_SOLICITATION;
+import static org.onosproject.routing.cpr.ControlPlaneRedirectManager.ACL_PRIORITY;
+import static org.onosproject.routing.cpr.ControlPlaneRedirectManager.buildArpSelector;
+import static org.onosproject.routing.cpr.ControlPlaneRedirectManager.buildIPDstSelector;
+import static org.onosproject.routing.cpr.ControlPlaneRedirectManager.buildIPSrcSelector;
+import static org.onosproject.routing.cpr.ControlPlaneRedirectManager.buildNdpSelector;
+
+/**
+ * UnitTests for ControlPlaneRedirectManager.
+ */
+@Ignore("Too many dependencies on internal implementation, too hard to maintain")
+public class ControlPlaneRedirectManagerTest {
+
+    private DeviceService deviceService;
+    private FlowObjectiveService flowObjectiveService;
+    private NetworkConfigService networkConfigService;
+    private final Set<Interface> interfaces = Sets.newHashSet();
+    static Device dev3 = NetTestTools.device("0000000000000001");
+    private static final int OSPF_IP_PROTO = 0x59;
+    private CoreService coreService = new TestCoreService();
+    private InterfaceService interfaceService;
+    private static final ApplicationId APPID = TestApplicationId.create("org.onosproject.vrouter");
+
+    private static final DeviceId DEVICE_ID = DeviceId.deviceId("of:0000000000000001");
+
+    private ConnectPoint controlPlaneConnectPoint = new ConnectPoint(DEVICE_ID,
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW1_ETH1 = new ConnectPoint(DEVICE_ID,
+            PortNumber.portNumber(1));
+
+    private static final ConnectPoint SW1_ETH2 = new ConnectPoint(DEVICE_ID,
+            PortNumber.portNumber(2));
+
+    private static final ConnectPoint SW1_ETH3 = new ConnectPoint(DEVICE_ID,
+            PortNumber.portNumber(3));
+
+    private ControlPlaneRedirectManager controlPlaneRedirectManager = new ControlPlaneRedirectManager();
+    private RouterConfig routerConfig = new TestRouterConfig();
+    private NetworkConfigListener networkConfigListener;
+    private DeviceListener deviceListener;
+    private MastershipService mastershipService = new InternalMastershipServiceTest();
+    private InterfaceListener interfaceListener;
+    private ApplicationService applicationService;
+
+    @Before
+    public void setUp() {
+        networkConfigListener = createMock(NetworkConfigListener.class);
+        deviceService = new TestDeviceService();
+        deviceListener = createMock(DeviceListener.class);
+
+        interfaceListener = createMock(InterfaceListener.class);
+        deviceService.addListener(deviceListener);
+        setUpInterfaceService();
+        interfaceService = new InternalInterfaceService();
+        interfaceService.addListener(interfaceListener);
+        networkConfigService = new TestNetworkConfigService();
+        networkConfigService.addListener(networkConfigListener);
+        flowObjectiveService = createMock(FlowObjectiveService.class);
+        applicationService = createNiceMock(ApplicationService.class);
+        replay(applicationService);
+        setUpFlowObjectiveService();
+        controlPlaneRedirectManager.coreService = coreService;
+        controlPlaneRedirectManager.flowObjectiveService = flowObjectiveService;
+        controlPlaneRedirectManager.networkConfigService = networkConfigService;
+        controlPlaneRedirectManager.interfaceService = interfaceService;
+        controlPlaneRedirectManager.deviceService = deviceService;
+        controlPlaneRedirectManager.hostService = createNiceMock(HostService.class);
+        controlPlaneRedirectManager.mastershipService = mastershipService;
+        controlPlaneRedirectManager.applicationService = applicationService;
+        controlPlaneRedirectManager.activate();
+        verify(flowObjectiveService);
+    }
+
+    /**
+     * Tests adding new Device to a openflow router.
+     */
+    @Test
+    public void testAddDevice() {
+        ConnectPoint sw1eth4 = new ConnectPoint(DEVICE_ID, PortNumber.portNumber(4));
+        List<InterfaceIpAddress> interfaceIpAddresses = new ArrayList<>();
+        interfaceIpAddresses.add(
+                new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"), IpPrefix.valueOf("192.168.40.0/24"))
+        );
+        interfaceIpAddresses.add(
+                new InterfaceIpAddress(IpAddress.valueOf("2000::ff"), IpPrefix.valueOf("2000::ff/120"))
+        );
+
+        Interface sw1Eth4 = new Interface(sw1eth4.deviceId().toString(), sw1eth4, interfaceIpAddresses,
+                MacAddress.valueOf("00:00:00:00:00:04"), VlanId.NONE);
+        interfaces.add(sw1Eth4);
+        EasyMock.reset(flowObjectiveService);
+        setUpFlowObjectiveService();
+        deviceListener.event(new DeviceEvent(DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED, dev3));
+        verify(flowObjectiveService);
+    }
+
+    /**
+     * Tests adding while updating the networkConfig.
+     */
+    @Test
+    public void testUpdateNetworkConfig() {
+        ConnectPoint sw1eth4 = new ConnectPoint(DEVICE_ID, PortNumber.portNumber(4));
+        List<InterfaceIpAddress> interfaceIpAddresses = new ArrayList<>();
+        interfaceIpAddresses.add(
+                new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"), IpPrefix.valueOf("192.168.40.0/24"))
+        );
+        interfaceIpAddresses.add(
+                new InterfaceIpAddress(IpAddress.valueOf("2000::ff"), IpPrefix.valueOf("2000::ff/120"))
+        );
+
+        Interface sw1Eth4 = new Interface(sw1eth4.deviceId().toString(), sw1eth4, interfaceIpAddresses,
+                MacAddress.valueOf("00:00:00:00:00:04"), VlanId.NONE);
+        interfaces.add(sw1Eth4);
+        EasyMock.reset(flowObjectiveService);
+        setUpFlowObjectiveService();
+        networkConfigListener
+                .event(new NetworkConfigEvent(Type.CONFIG_UPDATED, dev3, RoutingService.ROUTER_CONFIG_CLASS));
+        networkConfigService.addListener(networkConfigListener);
+        verify(flowObjectiveService);
+    }
+
+    /**
+     * Tests adding while updating the networkConfig.
+     */
+    @Test
+    public void testAddInterface() {
+        ConnectPoint sw1eth4 = new ConnectPoint(DEVICE_ID, PortNumber.portNumber(4));
+        List<InterfaceIpAddress> interfaceIpAddresses = new ArrayList<>();
+        interfaceIpAddresses.add(
+                new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"), IpPrefix.valueOf("192.168.40.0/24"))
+        );
+        interfaceIpAddresses.add(
+                new InterfaceIpAddress(IpAddress.valueOf("2000::ff"), IpPrefix.valueOf("2000::ff/120"))
+        );
+
+        Interface sw1Eth4 = new Interface(sw1eth4.deviceId().toString(), sw1eth4, interfaceIpAddresses,
+                MacAddress.valueOf("00:00:00:00:00:04"), VlanId.NONE);
+        interfaces.add(sw1Eth4);
+
+        EasyMock.reset(flowObjectiveService);
+        expect(flowObjectiveService.allocateNextId()).andReturn(1).anyTimes();
+
+        setUpInterfaceConfiguration(sw1Eth4, true);
+        replay(flowObjectiveService);
+        interfaceListener.event(new InterfaceEvent(InterfaceEvent.Type.INTERFACE_ADDED, sw1Eth4, 500L));
+        verify(flowObjectiveService);
+    }
+
+    @Test
+    public void testRemoveInterface() {
+        ConnectPoint sw1eth4 = new ConnectPoint(DEVICE_ID, PortNumber.portNumber(4));
+        List<InterfaceIpAddress> interfaceIpAddresses = new ArrayList<>();
+        interfaceIpAddresses.add(
+                new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"), IpPrefix.valueOf("192.168.40.0/24"))
+        );
+        interfaceIpAddresses.add(
+                new InterfaceIpAddress(IpAddress.valueOf("2000::ff"), IpPrefix.valueOf("2000::ff/120"))
+        );
+
+        Interface sw1Eth4 = new Interface(sw1eth4.deviceId().toString(), sw1eth4, interfaceIpAddresses,
+                MacAddress.valueOf("00:00:00:00:00:04"), VlanId.NONE);
+        EasyMock.reset(flowObjectiveService);
+        expect(flowObjectiveService.allocateNextId()).andReturn(1).anyTimes();
+
+        setUpInterfaceConfiguration(sw1Eth4, false);
+        replay(flowObjectiveService);
+        interfaceListener.event(new InterfaceEvent(InterfaceEvent.Type.INTERFACE_REMOVED, sw1Eth4, 500L));
+        verify(flowObjectiveService);
+    }
+
+    /**
+     * Setup flow Configuration for all configured Interfaces.
+     *
+     **/
+    private void setUpFlowObjectiveService() {
+        expect(flowObjectiveService.allocateNextId()).andReturn(1).anyTimes();
+        for (Interface intf : interfaceService.getInterfaces()) {
+            setUpInterfaceConfiguration(intf, true);
+        }
+        replay(flowObjectiveService);
+    }
+
+    /**
+     * Setting up flowobjective expectations for basic forwarding and ospf.
+     **/
+    private void setUpInterfaceConfiguration(Interface intf, boolean install) {
+        DeviceId deviceId = controlPlaneConnectPoint.deviceId();
+        PortNumber controlPlanePort = controlPlaneConnectPoint.port();
+
+        for (InterfaceIpAddress ip : intf.ipAddresses()) {
+            int cpNextId, intfNextId;
+
+            cpNextId = modifyNextObjective(deviceId, controlPlanePort,
+                    VlanId.vlanId(ControlPlaneRedirectManager.ASSIGNED_VLAN), true, install);
+            intfNextId = modifyNextObjective(deviceId, intf.connectPoint().port(),
+                    VlanId.vlanId(ControlPlaneRedirectManager.ASSIGNED_VLAN), true, install);
+
+            // IP to router
+            TrafficSelector toSelector = buildIPDstSelector(
+                    ip.ipAddress().toIpPrefix(),
+                    intf.connectPoint().port(),
+                    null,
+                    intf.mac(),
+                    intf.vlan());
+
+            flowObjectiveService.forward(deviceId, buildForwardingObjective(toSelector, null,
+                                                                            cpNextId, install, ACL_PRIORITY));
+            expectLastCall().once();
+
+            // IP from router
+            TrafficSelector fromSelector = buildIPSrcSelector(
+                    ip.ipAddress().toIpPrefix(),
+                    controlPlanePort,
+                    intf.mac(),
+                    null,
+                    intf.vlan()
+            );
+
+            flowObjectiveService.forward(deviceId, buildForwardingObjective(fromSelector, null,
+                                                                            intfNextId, install, ACL_PRIORITY));
+            expectLastCall().once();
+
+            TrafficTreatment puntTreatment = DefaultTrafficTreatment.builder().punt().build();
+            if (ip.ipAddress().isIp4()) {
+                // ARP to router
+                toSelector = buildArpSelector(
+                        intf.connectPoint().port(),
+                        intf.vlan(),
+                        null,
+                        null
+                );
+
+                flowObjectiveService.forward(deviceId, buildForwardingObjective(toSelector, puntTreatment,
+                                                                                cpNextId, install, ACL_PRIORITY + 1));
+                expectLastCall().once();
+
+                // ARP from router
+                fromSelector = buildArpSelector(
+                        controlPlanePort,
+                        intf.vlan(),
+                        ip.ipAddress().getIp4Address(),
+                        intf.mac()
+                );
+
+                flowObjectiveService.forward(deviceId,
+                                             buildForwardingObjective(fromSelector, puntTreatment,
+                                                                      intfNextId, install, ACL_PRIORITY + 1));
+                expectLastCall().once();
+            } else {
+                // NDP solicitation to router
+                toSelector = buildNdpSelector(
+                        intf.connectPoint().port(),
+                        intf.vlan(),
+                        null,
+                        NEIGHBOR_SOLICITATION,
+                        null
+                );
+
+                flowObjectiveService.forward(deviceId, buildForwardingObjective(toSelector, puntTreatment,
+                                                                                cpNextId, install, ACL_PRIORITY + 1));
+                expectLastCall().once();
+
+                // NDP solicitation from router
+                fromSelector = buildNdpSelector(
+                        controlPlanePort,
+                        intf.vlan(),
+                        ip.ipAddress().toIpPrefix(),
+                        NEIGHBOR_SOLICITATION,
+                        intf.mac()
+                );
+
+                flowObjectiveService.forward(deviceId,
+                                             buildForwardingObjective(fromSelector, puntTreatment,
+                                                                      intfNextId, install, ACL_PRIORITY + 1));
+
+                // NDP advertisement to router
+                toSelector = buildNdpSelector(
+                        intf.connectPoint().port(),
+                        intf.vlan(),
+                        null,
+                        NEIGHBOR_ADVERTISEMENT,
+                        null
+                );
+
+                flowObjectiveService.forward(deviceId, buildForwardingObjective(toSelector, puntTreatment,
+                                                                                cpNextId, install, ACL_PRIORITY + 1));
+                expectLastCall().once();
+
+                // NDP advertisement from router
+                fromSelector = buildNdpSelector(
+                        controlPlanePort,
+                        intf.vlan(),
+                        ip.ipAddress().toIpPrefix(),
+                        NEIGHBOR_ADVERTISEMENT,
+                        intf.mac()
+                );
+
+                flowObjectiveService.forward(deviceId,
+                                             buildForwardingObjective(fromSelector, puntTreatment,
+                                                                      intfNextId, install, ACL_PRIORITY + 1));
+            }
+        }
+        // setting expectations for ospf forwarding.
+        TrafficSelector toSelector = DefaultTrafficSelector.builder().matchInPort(intf.connectPoint().port())
+                .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).matchVlanId(intf.vlan())
+                .matchIPProtocol((byte) OSPF_IP_PROTO).build();
+
+        modifyNextObjective(deviceId, controlPlanePort, VlanId.vlanId((short) 4094), true, install);
+        flowObjectiveService.forward(controlPlaneConnectPoint.deviceId(),
+                buildForwardingObjective(toSelector, null, 1, install, 40001));
+        expectLastCall().once();
+    }
+
+    /**
+     * Setup expectations on flowObjectiveService.next for NextObjective.
+     *
+     **/
+    private int modifyNextObjective(DeviceId deviceId, PortNumber portNumber, VlanId vlanId, boolean popVlan,
+            boolean modifyFlag) {
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective.builder().withId(1)
+                .withType(NextObjective.Type.SIMPLE).fromApp(APPID);
+
+        TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
+        if (popVlan) {
+            ttBuilder.popVlan();
+        }
+        ttBuilder.setOutput(portNumber);
+
+        TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
+        metabuilder.matchVlanId(vlanId);
+
+        nextObjBuilder.withMeta(metabuilder.build());
+        nextObjBuilder.addTreatment(ttBuilder.build());
+        if (modifyFlag) {
+            flowObjectiveService.next(deviceId, nextObjBuilder.add());
+            expectLastCall().once();
+        } else {
+            flowObjectiveService.next(deviceId, nextObjBuilder.remove());
+            expectLastCall().once();
+        }
+        return 1;
+    }
+
+    /**
+     * Setup Interface expectation for all Testcases.
+     **/
+    private void setUpInterfaceService() {
+        List<InterfaceIpAddress> interfaceIpAddresses1 = new ArrayList<>();
+        interfaceIpAddresses1
+                .add(new InterfaceIpAddress(IpAddress.valueOf("192.168.10.101"), IpPrefix.valueOf("192.168.10.0/24")));
+        Interface sw1Eth1 = new Interface(SW1_ETH1.deviceId().toString(), SW1_ETH1, interfaceIpAddresses1,
+                MacAddress.valueOf("00:00:00:00:00:01"), VlanId.NONE);
+        interfaces.add(sw1Eth1);
+
+        List<InterfaceIpAddress> interfaceIpAddresses2 = new ArrayList<>();
+        interfaceIpAddresses2
+                .add(new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"), IpPrefix.valueOf("192.168.20.0/24")));
+        Interface sw1Eth2 = new Interface(SW1_ETH1.deviceId().toString(), SW1_ETH2, interfaceIpAddresses2,
+                MacAddress.valueOf("00:00:00:00:00:02"), VlanId.NONE);
+        interfaces.add(sw1Eth2);
+
+        List<InterfaceIpAddress> interfaceIpAddresses3 = new ArrayList<>();
+        interfaceIpAddresses3
+                .add(new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"), IpPrefix.valueOf("192.168.30.0/24")));
+        Interface sw1Eth3 = new Interface(SW1_ETH1.deviceId().toString(), SW1_ETH3, interfaceIpAddresses3,
+                MacAddress.valueOf("00:00:00:00:00:03"), VlanId.NONE);
+        interfaces.add(sw1Eth3);
+
+    }
+
+    private ForwardingObjective buildForwardingObjective(TrafficSelector selector, TrafficTreatment treatment,
+            int nextId, boolean add, int priority) {
+        DefaultForwardingObjective.Builder fobBuilder = DefaultForwardingObjective.builder();
+        fobBuilder.withSelector(selector);
+        if (treatment != null) {
+            fobBuilder.withTreatment(treatment);
+        }
+        if (nextId != -1) {
+            fobBuilder.nextStep(nextId);
+        }
+        fobBuilder.fromApp(APPID).withPriority(priority).withFlag(ForwardingObjective.Flag.VERSATILE);
+
+        return add ? fobBuilder.add() : fobBuilder.remove();
+    }
+
+    private class TestCoreService extends CoreServiceAdapter {
+
+        @Override
+        public ApplicationId getAppId(String name) {
+            return APPID;
+        }
+
+        @Override
+        public ApplicationId registerApplication(String name) {
+            return new TestApplicationId(name);
+        }
+
+    }
+
+    private class TestDeviceService extends DeviceServiceAdapter {
+
+        @Override
+        public boolean isAvailable(DeviceId deviceId) {
+            boolean flag = false;
+            if (deviceId.equals(controlPlaneConnectPoint.deviceId())) {
+                flag = true;
+            }
+            return flag;
+        }
+
+        @Override
+        public void addListener(DeviceListener listener) {
+            ControlPlaneRedirectManagerTest.this.deviceListener = listener;
+        }
+
+    }
+
+    private class TestRouterConfig extends RouterConfig {
+
+        @Override
+        public ConnectPoint getControlPlaneConnectPoint() {
+            return controlPlaneConnectPoint;
+        }
+
+        @Override
+        public boolean getOspfEnabled() {
+            return true;
+        }
+
+        @Override
+        public List<String> getInterfaces() {
+            ArrayList<String> interfaces = new ArrayList<>();
+            interfaces.add("of:0000000000000001");
+            interfaces.add("of:0000000000000001/2");
+            interfaces.add("of:0000000000000001/3");
+            return interfaces;
+        }
+
+    }
+
+    private class TestNetworkConfigService extends NetworkConfigServiceAdapter {
+
+        @Override
+        public void addListener(NetworkConfigListener listener) {
+            ControlPlaneRedirectManagerTest.this.networkConfigListener = listener;
+        }
+
+        @Override
+        public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
+            return (C) ControlPlaneRedirectManagerTest.this.routerConfig;
+        }
+
+    }
+
+    private static class TestApplicationId implements ApplicationId {
+
+        private final String name;
+        private final short id;
+
+        public TestApplicationId(String name) {
+            this.name = name;
+            this.id = (short) Objects.hash(name);
+        }
+
+        public static ApplicationId create(String name) {
+            return new TestApplicationId(name);
+        }
+
+        @Override
+        public short id() {
+            return id;
+        }
+
+        @Override
+        public String name() {
+            return name;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + id;
+            result = prime * result + ((name == null) ? 0 : name.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            TestApplicationId other = (TestApplicationId) obj;
+            if (id != other.id) {
+                return false;
+            }
+            if (name == null) {
+                if (other.name != null) {
+                    return false;
+                }
+            } else if (!name.equals(other.name)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    private class InternalMastershipServiceTest extends MastershipServiceAdapter {
+
+        @Override
+        public boolean isLocalMaster(DeviceId deviceId) {
+            boolean flag = deviceId.equals(controlPlaneConnectPoint.deviceId());
+            return flag;
+        }
+
+    }
+
+    private class InternalInterfaceService extends InterfaceServiceAdapter {
+
+        @Override
+        public void addListener(InterfaceListener listener) {
+            ControlPlaneRedirectManagerTest.this.interfaceListener = listener;
+        }
+
+        @Override
+        public Set<Interface> getInterfaces() {
+            return interfaces;
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+            Set<Interface> setIntf = new HashSet<Interface>();
+            for (Interface intf : interfaces) {
+                if (intf.connectPoint().equals(port)) {
+                    setIntf.add(intf);
+                }
+            }
+            return setIntf;
+        }
+
+        @Override
+        public Interface getMatchingInterface(IpAddress ip) {
+            Interface intff = null;
+            for (Interface intf : interfaces) {
+                for (InterfaceIpAddress address : intf.ipAddresses()) {
+                    if (address.ipAddress().equals(ip)) {
+                        intff = intf;
+                        break;
+                    }
+                }
+            }
+
+            return intff;
+        }
+
+    }
+}