Initial commit of SimpleFabric application

Change-Id: Ia491aa6ad1b15576a54803decfee54aa8c1d1d6a
diff --git a/apps/simplefabric/BUCK b/apps/simplefabric/BUCK
new file mode 100644
index 0000000..8125580
--- /dev/null
+++ b/apps/simplefabric/BUCK
@@ -0,0 +1,27 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//lib:JACKSON',
+    '//lib:concurrent-trees',
+]
+
+BUNDLES = [
+    '//apps/simplefabric:onos-apps-simplefabric',
+]
+
+TEST_DEPS = [
+    '//lib:TEST_ADAPTERS',
+]
+
+osgi_jar_with_tests (
+    deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
+)
+
+onos_app (
+  title = 'SimpleFabric',
+  category = 'Traffic Steering',
+  url = 'http://onosproject.org',
+  included_bundles = BUNDLES,
+  description = 'Simple Fabric application',
+  required_apps = [ 'org.onosproject.openflow-base', 'org.onosproject.lldpprovider', 'org.onosproject.hostprovider' ],
+)
diff --git a/apps/simplefabric/app.xml b/apps/simplefabric/app.xml
new file mode 100644
index 0000000..df5cc60
--- /dev/null
+++ b/apps/simplefabric/app.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+<app name="org.onosproject.simplefabric" origin="ON.Lab" version="${project.version}"
+     category="Traffic Steering" url="http://onosproject.org" title="Simple Leaf-Spine Network App"
+     featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
+     features="${project.artifactId}" apps="org.onosproject.openflow-base,org.onosproject.lldpprovider,org.onosproject.hostprovider">
+    <description>${project.description}</description>
+    <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
+</app>
diff --git a/apps/simplefabric/features.xml b/apps/simplefabric/features.xml
new file mode 100644
index 0000000..9ab23de
--- /dev/null
+++ b/apps/simplefabric/features.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  ~ 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.
+  -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+    <feature name="onos-app-simplefabric" version="${project.version}"
+             description="${project.description}">
+        <feature>onos-api</feature>
+        <bundle>mvn:${project.groupId}/onos-app-simplefabric/${project.version}</bundle>
+    </feature>
+</features>
diff --git a/apps/simplefabric/mininet-simplefabric.py b/apps/simplefabric/mininet-simplefabric.py
new file mode 100755
index 0000000..dafbbc6
--- /dev/null
+++ b/apps/simplefabric/mininet-simplefabric.py
@@ -0,0 +1,94 @@
+#!/usr/bin/python
+# Mininet model for Simple Leaf-Spine Network
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Host, OVSSwitch, RemoteController
+from mininet.log import setLogLevel
+from mininet.cli import CLI
+
+
+"Create custom topo."
+
+net = Mininet()
+
+# Add leaf switch and hosts in rack 1
+# subnet: 10.0.1.0/24
+s10 = net.addSwitch('s10', dpid='0000000000000011')
+h11 = net.addHost('h11', mac='00:00:10:00:01:11', ip='10.0.1.11/24', defaultRoute='via 10.0.1.1')
+h12 = net.addHost('h12', mac='00:00:10:00:01:12', ip='10.0.1.12/24', defaultRoute='via 10.0.1.1')
+h13 = net.addHost('h13', mac='00:00:10:00:01:13', ip='10.0.1.13/24', defaultRoute='via 10.0.1.1')
+h14 = net.addHost('h14', mac='00:00:10:00:01:14', ip='10.0.1.14/24', defaultRoute='via 10.0.1.1')
+d11 = net.addHost('d11', mac='00:00:10:00:01:a1', ip='10.0.1.111/24', defaultRoute='via 10.0.1.1')
+d12 = net.addHost('d12', mac='00:00:10:00:01:a2', ip='10.0.1.112/24', defaultRoute='via 10.0.1.1')
+net.addLink(s10, h11)
+net.addLink(s10, h12)
+net.addLink(s10, h13)
+net.addLink(s10, h14)
+net.addLink(s10, d11)
+net.addLink(s10, d12)
+
+# Add leaf switch and hosts in rack 2
+# subnet: 10.0.2.0/24
+s20 = net.addSwitch('s20', dpid='0000000000000012')
+h21 = net.addHost('h21', mac='00:00:10:00:02:21', ip='10.0.2.21/24', defaultRoute='via 10.0.2.1')
+h22 = net.addHost('h22', mac='00:00:10:00:02:22', ip='10.0.2.22/24', defaultRoute='via 10.0.2.1')
+h23 = net.addHost('h23', mac='00:00:10:00:02:23', ip='10.0.2.23/24', defaultRoute='via 10.0.2.1')
+h24 = net.addHost('h24', mac='00:00:10:00:02:24', ip='10.0.2.24/24', defaultRoute='via 10.0.2.1')
+d21 = net.addHost('d21', mac='00:00:10:00:02:b1', ip='10.0.2.221/24', defaultRoute='via 10.0.2.1')
+d22 = net.addHost('d22', mac='00:00:10:00:02:b2', ip='10.0.2.222/24', defaultRoute='via 10.0.2.1')
+net.addLink(s20, h21)
+net.addLink(s20, h22)
+net.addLink(s20, h23)
+net.addLink(s20, h24)
+net.addLink(s20, d21)
+net.addLink(s20, d22)
+
+# Add spine switches and nat
+# subnet: 10.0.0.0/16
+ss1 = net.addSwitch('ss1', dpid='0000000000000021')
+ss2 = net.addSwitch('ss2', dpid='0000000000000022')
+net.addLink(ss1, s10)
+net.addLink(ss1, s20)
+net.addLink(ss2, s10)
+net.addLink(ss2, s20)
+
+# Add External Router
+#h31 = net.addHost('h31', mac='00:00:10:00:00:31', ip='10.0.0.31/24', defaultRoute='via 10.0.0.1')
+#h32 = net.addHost('h32', mac='00:00:10:00:00:32', ip='10.0.0.32/24', defaultRoute='via 10.0.0.1')
+#net.addLink(ss1, h31);
+#net.addLink(ss2, h32);
+
+# Add ONOS/RemoteController; set your ONOS node ip address
+net.addController(RemoteController('c1', ip='10.10.108.140'))
+
+# Main
+setLogLevel('info')
+net.start()
+
+# reveal hosts to switches
+#for h in [h11, h12, h13, h14, d11, d12] :
+#  net.ping(hosts=[h, h31], timeout='1')
+#  net.ping(hosts=[h, h31], timeout='1')
+#for h in [h21, h22, h23, h24, d21, d22] :
+#  net.ping(hosts=[h, h32], timeout='1')
+#  net.ping(hosts=[h, h32], timeout='1')
+
+# do interactive shell
+CLI(net)
+net.stop()
+
diff --git a/apps/simplefabric/network-cfg.json b/apps/simplefabric/network-cfg.json
new file mode 100644
index 0000000..f988497
--- /dev/null
+++ b/apps/simplefabric/network-cfg.json
@@ -0,0 +1,56 @@
+{
+
+  "devices":{
+    "of:0000000000000011":{ "basic":{ "name":"LS1", "latitude":35, "longitude":-100 } },
+    "of:0000000000000012":{ "basic":{ "name":"LS2", "latitude":35, "longitude":-90  } },
+    "of:0000000000000021":{ "basic":{ "name":"SS1", "latitude":40, "longitude":-100 } },
+    "of:0000000000000022":{ "basic":{ "name":"SS2", "latitude":40, "longitude":-90  } }
+  },
+
+  "ports" : {
+    "of:0000000000000011/1" : { "interfaces" : [ { "name" : "h11" } ] },
+    "of:0000000000000011/2" : { "interfaces" : [ { "name" : "h12" } ] },
+    "of:0000000000000011/3" : { "interfaces" : [ { "name" : "h13" } ] },
+    "of:0000000000000011/4" : { "interfaces" : [ { "name" : "h14" } ] },
+    "of:0000000000000011/5" : { "interfaces" : [ { "name" : "d11" } ] },
+    "of:0000000000000011/6" : { "interfaces" : [ { "name" : "d12" } ] },
+    "of:0000000000000011/7" : { "interfaces" : [ { "name" : "LS1_SS1" } ] },
+    "of:0000000000000011/8" : { "interfaces" : [ { "name" : "LS1_SS2" } ] },
+
+    "of:0000000000000012/1" : { "interfaces" : [ { "name" : "h21" } ] } ,
+    "of:0000000000000012/2" : { "interfaces" : [ { "name" : "h22" } ] },
+    "of:0000000000000012/3" : { "interfaces" : [ { "name" : "h23" } ] },
+    "of:0000000000000012/4" : { "interfaces" : [ { "name" : "h24" } ] },
+    "of:0000000000000012/5" : { "interfaces" : [ { "name" : "d21" } ] },
+    "of:0000000000000012/6" : { "interfaces" : [ { "name" : "d22" } ] },
+    "of:0000000000000012/7" : { "interfaces" : [ { "name" : "LS2_SS1" } ] },
+    "of:0000000000000012/8" : { "interfaces" : [ { "name" : "LS2_SS2" } ] },
+
+    "of:0000000000000021/1" : { "interfaces" : [ { "name" : "SS1_LS1" } ] },
+    "of:0000000000000021/2" : { "interfaces" : [ { "name" : "SS1_LS2" } ] },
+
+    "of:0000000000000022/1" : { "interfaces" : [ { "name" : "SS2_LS1" } ] },
+    "of:0000000000000022/2" : { "interfaces" : [ { "name" : "SS2_LS2" } ] }
+
+  },
+
+  "apps" : {
+    "org.onosproject.simplefabric" : {
+      "simpleFabric" : {
+        "l2Networks" : [
+          { "name" : "LEAF1", "interfaces" : ["h11", "h12", "h13", "h14", "d11", "d12" ], "l2Forward" : true },
+          { "name" : "LEAF2", "interfaces" : ["h21", "h22", "h23", "h24", "d21", "d22" ], "l2Forward" : true }
+        ],
+        "ipSubnets" : [
+           { "ipPrefix" : "10.0.1.0/24", "gatewayIp" : "10.0.1.1", "gatewayMac" : "00:00:10:00:01:01", "l2NetworkName" : "LEAF1" },
+           { "ipPrefix" : "10.0.2.0/24", "gatewayIp" : "10.0.2.1", "gatewayMac" : "00:00:10:00:02:01", "l2NetworkName" : "LEAF2" }
+        ],
+        "borderRoutes" : [
+           { "ipPrefix" : "0.0.0.0/0", "nextHop" : "10.0.1.2" }
+        ]
+      }
+    }
+  }
+
+}
+
diff --git a/apps/simplefabric/pom.xml b/apps/simplefabric/pom.xml
new file mode 100644
index 0000000..ef36955
--- /dev/null
+++ b/apps/simplefabric/pom.xml
@@ -0,0 +1,154 @@
+<?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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-apps</artifactId>
+        <version>1.12.0-SNAPSHOT</version>
+        <relativePath>../../onos</relativePath>
+    </parent>
+
+    <artifactId>onos-app-simplefabric</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>Simple Fabric Application for Leaf-Spine L2/L3 Network</description>
+
+    <properties>
+        <onos.app.name>org.onosproject.simplefabric</onos.app.name>
+        <onos.app.category>Traffic Steering</onos.app.category>
+        <onos.app.title>Simple Fabric App</onos.app.title>
+        <onos.app.url>http://onosproject.org</onos.app.url>
+
+        <web.context>/onos/v1/simplefabric</web.context>
+        <api.version>1.0.0</api.version>
+        <api.title>Simple Fabric Application REST API</api.title>
+        <api.description>APIs for interacting with the Simple Fabric application.</api.description>
+        <api.package>org.onosproject.simplefabric</api.package>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-misc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-serializers</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-core-net</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-rest</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <_wab>src/main/webapp/</_wab>
+                        <Include-Resource>
+                            WEB-INF/classes/apidoc/swagger.json=target/swagger.json,
+                            {maven-resources}
+                        </Include-Resource>
+                        <Bundle-SymbolicName>
+                            ${project.groupId}.${project.artifactId}
+                        </Bundle-SymbolicName>
+                        <Import-Package>
+                            *,org.glassfish.jersey.servlet
+                        </Import-Package>
+                        <Web-ContextPath>${web.context}</Web-ContextPath>
+                    </instructions>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+
+            <plugin>
+                <groupId>org.onosproject</groupId>
+                <artifactId>onos-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/IpSubnet.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/IpSubnet.java
new file mode 100644
index 0000000..596c659
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/IpSubnet.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.EncapsulationType;
+
+import java.util.Objects;
+
+/**
+ * Configuration details for an ip subnet entry.
+ */
+public class IpSubnet {
+    private final IpPrefix ipPrefix;
+    private final IpAddress gatewayIp;
+    private final MacAddress gatewayMac;
+    private EncapsulationType encapsulation;
+    private final String l2NetworkName;
+
+    /**
+     * Creates a new ip subnet entry.
+     *
+     * @param ipPrefix  an ip subnet
+     * @param gatewayIp IP of the virtual gateway
+     * @param gatewayMac MacAddress of the virtual gateway
+     * @param encapsulation EnacaptulatioType for routes related to this subnet
+     * @param l2NetworkName Name of L2 Network this subnet is bound
+     */
+    public IpSubnet(IpPrefix ipPrefix, IpAddress gatewayIp, MacAddress gatewayMac,
+                    EncapsulationType encapsulation, String l2NetworkName) {
+        this.ipPrefix = ipPrefix;
+        this.gatewayIp = gatewayIp;
+        this.gatewayMac = gatewayMac;
+        this.encapsulation = EncapsulationType.NONE;
+        this.l2NetworkName = l2NetworkName;
+    }
+
+    /**
+     * Gets the ip subnet of the ip subnet entry.
+     *
+     * @return the ip subnet
+     */
+    public IpPrefix ipPrefix() {
+        return ipPrefix;
+    }
+
+    /**
+     * Gets the virtual gateway IP address of the ip subnet entry.
+     *
+     * @return the virtual gateway IP address
+     */
+    public IpAddress gatewayIp() {
+        return gatewayIp;
+    }
+
+    /**
+     * Gets the virtual gateway Mac address of the ip subnet entry.
+     *
+     * @return the virtuai gateway Mac address
+     */
+    public MacAddress gatewayMac() {
+        return gatewayMac;
+    }
+
+    /**
+     * Gets the encapsulation type of ip subnet entry.
+     *
+     * @return the encapsulation type
+     */
+    public EncapsulationType encapsulation() {
+        return encapsulation;
+    }
+
+    /**
+     * Gets the name of L2 Network this subnet is bound.
+     *
+     * @return the l2Network name this subnet is allocated
+     */
+    public String l2NetworkName() {
+        return l2NetworkName;
+    }
+
+    /**
+     * Tests whether the IP version of this entry is IPv4.
+     *
+     * @return true if the IP version of this entry is IPv4, otherwise false.
+     */
+    public boolean isIp4() {
+        return ipPrefix.isIp4();
+    }
+
+    /**
+     * Tests whether the IP version of this entry is IPv6.
+     *
+     * @return true if the IP version of this entry is IPv6, otherwise false.
+     */
+    public boolean isIp6() {
+        return ipPrefix.isIp6();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(ipPrefix, gatewayIp, gatewayMac, encapsulation, l2NetworkName);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof IpSubnet)) {
+            return false;
+        }
+        IpSubnet that = (IpSubnet) obj;
+        return Objects.equals(this.ipPrefix, that.ipPrefix)
+               && Objects.equals(this.gatewayIp, that.gatewayIp)
+               && Objects.equals(this.gatewayMac, that.gatewayMac)
+               && Objects.equals(this.encapsulation, that.encapsulation)
+               && Objects.equals(this.l2NetworkName, that.l2NetworkName);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("ipPrefix", ipPrefix)
+                .add("gatewayIp", gatewayIp)
+                .add("gatewayMac", gatewayMac)
+                .add("encapsulation", encapsulation)
+                .add("l2NetworkName", l2NetworkName)
+                .toString();
+    }
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/L2Network.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/L2Network.java
new file mode 100644
index 0000000..31e8cc0
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/L2Network.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.EncapsulationType;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+
+/**
+ * Class stores a L2Network information.
+ */
+public final class L2Network {
+
+    private String name;                  // also for network configuration
+    private Set<String> interfaceNames;   // also for network configuration
+    private EncapsulationType encapsulation;  // also for network configuration
+    private boolean l2Forward;            // do l2Forward (default:true) or not
+
+    /* status variables */
+    private Set<Interface> interfaces;    // available interfaces from interfaceNames
+    private Set<HostId> hostIds;          // available hosts from interfaces
+    private boolean dirty;
+
+    /**
+     * Constructs a L2Network data for Config value.
+     *
+     * @param name the given name
+     * @param ifaceNames the interface names
+     * @param encapsulation the encapsulation type
+     * @param l2Forward flag for l2Forward intents to be installed or not
+     */
+    L2Network(String name, Collection<String> ifaceNames, EncapsulationType encapsulation, boolean l2Forward) {
+        this.name = name;
+        this.interfaceNames = Sets.newHashSet();
+        this.interfaceNames.addAll(ifaceNames);
+        this.encapsulation = encapsulation;
+        this.l2Forward = (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) ? l2Forward : false;
+        this.interfaces = Sets.newHashSet();
+        this.hostIds = Sets.newHashSet();
+        this.dirty = false;
+    }
+
+    /**
+     * Constructs a L2Network data by given name and encapsulation type.
+     *
+     * @param name the given name
+     * @param encapsulation the encapsulation type
+     */
+    private L2Network(String name, EncapsulationType encapsulation) {
+        this.name = name;
+        this.interfaceNames = Sets.newHashSet();
+        this.encapsulation = encapsulation;
+        this.l2Forward = (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) ? true : false;
+        this.interfaces = Sets.newHashSet();
+        this.hostIds = Sets.newHashSet();
+        this.dirty = false;
+    }
+
+    /**
+     * Creates a L2Network data by given name.
+     * The encapsulation type of the L2Network will be NONE.
+     *
+     * @param name the given name
+     * @return the L2Network data
+     */
+    public static L2Network of(String name) {
+        Objects.requireNonNull(name);
+        return new L2Network(name, EncapsulationType.NONE);
+    }
+
+    /**
+     * Creates a copy of L2Network data.
+     *
+     * @param l2Network the L2Network data
+     * @return the copy of the L2Network data
+     */
+    public static L2Network of(L2Network l2Network) {
+        Objects.requireNonNull(l2Network);
+        L2Network l2NetworkCopy = new L2Network(l2Network.name(), l2Network.encapsulation());
+        l2NetworkCopy.interfaceNames.addAll(l2Network.interfaceNames());
+        l2NetworkCopy.l2Forward = (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) ? l2Network.l2Forward() : false;
+        l2NetworkCopy.interfaces.addAll(l2Network.interfaces());
+        l2NetworkCopy.hostIds.addAll(l2Network.hostIds());
+        l2NetworkCopy.setDirty(l2Network.dirty());
+        return l2NetworkCopy;
+    }
+
+    // field queries
+
+    /**
+     * Gets L2Network name.
+     *
+     * @return the name of L2Network
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * Gets L2Network interfaceNames.
+     *
+     * @return the interfaceNames of L2Network
+     */
+    public Set<String> interfaceNames() {
+        return ImmutableSet.copyOf(interfaceNames);
+    }
+
+    /**
+     * Gets L2Network encapsulation type.
+     *
+     * @return the encapsulation type of L2Network
+     */
+    public EncapsulationType encapsulation() {
+        return encapsulation;
+    }
+
+    /**
+     * Gets L2Network l2Forward flag.
+     *
+     * @return the l2Forward flag of L2Network
+     */
+    public boolean l2Forward() {
+        return l2Forward;
+    }
+
+    /**
+     * Gets L2Network interfaces.
+     *
+     * @return the interfaces of L2Network
+     */
+    public Set<Interface> interfaces() {
+        return ImmutableSet.copyOf(interfaces);
+    }
+
+    /**
+     * Gets L2Network hosts.
+     *
+     * @return the hosts of L2Network
+     */
+    public Set<HostId> hostIds() {
+        return ImmutableSet.copyOf(hostIds);
+    }
+
+    /**
+     * Gets L2Network dirty flag.
+     *
+     * @return the dirty flag of L2Network
+     */
+    public boolean dirty() {
+        return dirty;
+    }
+
+    /**
+     * Checks if the interface is of L2Network.
+     *
+     * @param iface the interface to be checked
+     * @return true if L2Network contains the interface
+     */
+    public boolean contains(Interface iface) {
+        return interfaces.contains(iface);
+    }
+
+    /**
+     * Checks if the ConnectPoint and Vlan is of L2Network.
+     *
+     * @param port the ConnectPoint to be checked
+     * @param vlanId the VlanId of the ConnectPoint to be checked
+     * @return true if L2Network contains the interface of the ConnnectPoint and VlanId
+     */
+    public boolean contains(ConnectPoint port, VlanId vlanId) {
+        for (Interface iface : interfaces) {
+            if (iface.connectPoint().equals(port) && iface.vlan().equals(vlanId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks if the DeviceId is of L2Network.
+     *
+     * @param deviceId the DeviceId to be checked
+     * @return true if L2Network contains any interface of the DeviceId
+     */
+    public boolean contains(DeviceId deviceId) {
+        for (Interface iface : interfaces) {
+            if (iface.connectPoint().deviceId().equals(deviceId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Adds interface to L2Network.
+     *
+     * @param iface the Interface to be added
+     */
+    public void addInterface(Interface iface) {
+        Objects.requireNonNull(iface);
+        if (interfaces.add(iface)) {
+            setDirty(true);
+        }
+    }
+
+    /**
+     * Adds host to L2Network.
+     *
+     * @param host the Host to be added
+     */
+    public void addHost(Host host) {
+        Objects.requireNonNull(host);
+        if (hostIds.add(host.id())) {
+            setDirty(true);
+        }
+    }
+
+    /**
+     * Sets L2Network dirty flag.
+     *
+     * @param newDirty the dirty flag to be set
+     */
+    public void setDirty(boolean newDirty) {
+        dirty = newDirty;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("name", name)
+                .add("interfaceNames", interfaceNames)
+                .add("encapsulation", encapsulation)
+                .add("l2Forward", l2Forward)
+                .add("interfaces", interfaces)
+                .add("hostIds", hostIds)
+                .add("dirty", dirty)
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof L2Network)) {
+            return false;
+        }
+        L2Network other = (L2Network) obj;
+        return Objects.equals(other.name, this.name)
+               && Objects.equals(other.interfaceNames, this.interfaceNames)
+               && Objects.equals(other.encapsulation, this.encapsulation)
+               && Objects.equals(other.l2Forward, this.l2Forward)
+               && Objects.equals(other.interfaces, this.interfaces)
+               && Objects.equals(other.hostIds, this.hostIds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, interfaces, encapsulation, l2Forward);
+    }
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/Route.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/Route.java
new file mode 100644
index 0000000..8eabb95
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/Route.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+/*
+ * local copy of onos/incubator/api/src/main/java/org/onosproject/incubator/net/routing/Route.java
+ * to remove dependency on onos.incubator.routing services, since 2017-08-09.
+ */
+
+package org.onosproject.simplefabric;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cluster.NodeId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a route.
+ */
+public class Route {
+
+    private static final String VERSION_MISMATCH =
+            "Prefix and next hop must be in the same address family";
+
+    private static final NodeId UNDEFINED = new NodeId("-");
+
+    /**
+     * Source of the route.
+     */
+    public enum Source {
+        /**
+         * Route came from the iBGP route source.
+         */
+        BGP,
+
+        /**
+         * Route came from the FPM route source.
+         */
+        FPM,
+
+        /**
+         * Route can from the static route source.
+         */
+        STATIC,
+
+        /**
+         * Route source was not defined.
+         */
+        UNDEFINED
+    }
+
+    private final Source source;
+    private final IpPrefix prefix;
+    private final IpAddress nextHop;
+    private final NodeId sourceNode;
+
+    /**
+     * Creates a route.
+     *
+     * @param source route source
+     * @param prefix IP prefix
+     * @param nextHop next hop IP address
+     */
+    public Route(Source source, IpPrefix prefix, IpAddress nextHop) {
+        this(source, prefix, nextHop, UNDEFINED);
+    }
+
+    /**
+     * Creates a route.
+     *
+     * @param source route source
+     * @param prefix IP prefix
+     * @param nextHop next hop IP address
+     * @param sourceNode ONOS node the route was sourced from
+     */
+    public Route(Source source, IpPrefix prefix, IpAddress nextHop, NodeId sourceNode) {
+        checkNotNull(prefix);
+        checkNotNull(nextHop);
+        checkArgument(prefix.version().equals(nextHop.version()), VERSION_MISMATCH);
+
+        this.source = checkNotNull(source);
+        this.prefix = prefix;
+        this.nextHop = nextHop;
+        this.sourceNode = checkNotNull(sourceNode);
+    }
+
+    /**
+     * Returns the route source.
+     *
+     * @return route source
+     */
+    public Source source() {
+        return source;
+    }
+
+    /**
+     * Returns the IP prefix of the route.
+     *
+     * @return IP prefix
+     */
+    public IpPrefix prefix() {
+        return prefix;
+    }
+
+    /**
+     * Returns the next hop IP address.
+     *
+     * @return next hop
+     */
+    public IpAddress nextHop() {
+        return nextHop;
+    }
+
+    /**
+     * Returns the ONOS node the route was sourced from.
+     *
+     * @return ONOS node ID
+     */
+    public NodeId sourceNode() {
+        return sourceNode;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(prefix, nextHop);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (!(other instanceof Route)) {
+            return false;
+        }
+
+        Route that = (Route) other;
+
+        return Objects.equals(this.prefix, that.prefix) &&
+                Objects.equals(this.nextHop, that.nextHop);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("prefix", prefix)
+                .add("nextHop", nextHop)
+                .toString();
+    }
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/RouteTools.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/RouteTools.java
new file mode 100644
index 0000000..b14438a
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/RouteTools.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/*
+ * local copy of onos/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteTools.java
+ * to remove dependency on onos.incubator.routing services, since 2017-08-09.
+ */
+
+package org.onosproject.simplefabric;
+
+import org.onlab.packet.IpPrefix;
+
+/**
+ * Routing tools.
+ */
+public final class RouteTools {
+
+    private RouteTools() {
+    }
+
+    /**
+     * Creates a binary string representation of an IP prefix.
+     *
+     * For each string, we put a extra "0" in the front. The purpose of
+     * doing this is to store the default route inside InvertedRadixTree.
+     *
+     * @param ipPrefix the IP prefix to use
+     * @return the binary string representation
+     */
+    public static String createBinaryString(IpPrefix ipPrefix) {
+        byte[] octets = ipPrefix.address().toOctets();
+        StringBuilder result = new StringBuilder(ipPrefix.prefixLength());
+        result.append("0");
+        for (int i = 0; i < ipPrefix.prefixLength(); i++) {
+            int byteOffset = i / Byte.SIZE;
+            int bitOffset = i % Byte.SIZE;
+            int mask = 1 << (Byte.SIZE - 1 - bitOffset);
+            byte value = octets[byteOffset];
+            boolean isSet = ((value & mask) != 0);
+            result.append(isSet ? "1" : "0");
+        }
+        return result.toString();
+    }
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java
new file mode 100644
index 0000000..a6d83b1
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.EncapsulationType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+/**
+ * Configuration object for prefix config.
+ */
+public class SimpleFabricConfig extends Config<ApplicationId> {
+    public static final String KEY = "simpleFabric";
+
+    private static final String L2NETWORKS = "l2Networks";
+    private static final String NAME = "name";
+    private static final String INTERFACES = "interfaces";
+    private static final String ENCAPSULATION = "encapsulation";
+    private static final String L2FORWARD = "l2Forward";
+    private static final String IPSUBNETS = "ipSubnets";
+    private static final String BORDERROUTES = "borderRoutes";
+    private static final String IPPREFIX = "ipPrefix";
+    private static final String GATEWAYIP = "gatewayIp";
+    private static final String GATEWAYMAC = "gatewayMac";
+    private static final String L2NETWORKNAME = "l2NetworkName";
+    private static final String NEXTHOP = "nextHop";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * Returns all l2Networks in this configuration.
+     *
+     * @return A set of L2Network.
+     */
+    public Set<L2Network> getL2Networks() {
+        Set<L2Network> l2Networks = Sets.newHashSet();
+        JsonNode l2NetworkNode = object.get(L2NETWORKS);
+        if (l2NetworkNode == null) {
+            return l2Networks;
+        }
+
+        l2NetworkNode.forEach(jsonNode -> {
+            Set<String> ifaces = Sets.newHashSet();
+            JsonNode l2NetworkIfaces = jsonNode.path(INTERFACES);
+            if (l2NetworkIfaces == null) {
+                log.warn("simple fabric network config cannot find {}; skip: jsonNode={}", INTERFACES, jsonNode);
+            } else if (!l2NetworkIfaces.toString().isEmpty()) {
+                l2NetworkIfaces.forEach(ifacesNode -> ifaces.add(new String(ifacesNode.asText())));
+            }
+            String encapsulation = "NONE";   // NONE or VLAN
+            if (jsonNode.hasNonNull(ENCAPSULATION)) {
+                encapsulation = jsonNode.get(ENCAPSULATION).asText();
+            }
+            boolean l2Forward = true;
+            if (jsonNode.hasNonNull(L2FORWARD)) {
+                l2Forward = jsonNode.get(L2FORWARD).asBoolean();
+            }
+            try {
+                l2Networks.add(new L2Network(
+                        jsonNode.get(NAME).asText(),
+                        ifaces,
+                        EncapsulationType.enumFromString(encapsulation),
+                        l2Forward));
+            } catch (Exception e) {
+                log.warn("simple fabric network config l2Network parse failed; skip: error={} jsonNode={}", jsonNode);
+            }
+        });
+        return l2Networks;
+    }
+
+    /**
+     * Gets the set of configured local IP subnets.
+     *
+     * @return IP Subnets
+     */
+    public Set<IpSubnet> ipSubnets() {
+        Set<IpSubnet> subnets = Sets.newHashSet();
+        JsonNode subnetsNode = object.get(IPSUBNETS);
+        if (subnetsNode == null) {
+            log.warn("simple fabric network config ipSubnets is null!");
+            return subnets;
+        }
+
+        subnetsNode.forEach(jsonNode -> {
+            String encapsulation = "NONE";   // NONE or VLAN
+            if (jsonNode.hasNonNull(ENCAPSULATION)) {
+                encapsulation = jsonNode.get(ENCAPSULATION).asText();
+            }
+            try {
+                subnets.add(new IpSubnet(
+                        IpPrefix.valueOf(jsonNode.get(IPPREFIX).asText()),
+                        IpAddress.valueOf(jsonNode.get(GATEWAYIP).asText()),
+                        MacAddress.valueOf(jsonNode.get(GATEWAYMAC).asText()),
+                        EncapsulationType.enumFromString(encapsulation),
+                        jsonNode.get(L2NETWORKNAME).asText()));
+            } catch (Exception e) {
+                log.warn("simple fabric network config ipSubnet parse failed; skip: error={} jsonNode={}", jsonNode);
+            }
+        });
+
+        return subnets;
+    }
+
+    /**
+     * Returns all routes in this configuration.
+     *
+     * @return A set of route.
+     */
+    public Set<Route> borderRoutes() {
+        Set<Route> routes = Sets.newHashSet();
+
+        JsonNode routesNode = object.get(BORDERROUTES);
+        if (routesNode == null) {
+            //log.warn("simple fabric network config borderRoutes is null!");
+            return routes;
+        }
+
+        routesNode.forEach(jsonNode -> {
+            try {
+                routes.add(new Route(
+                      Route.Source.STATIC,
+                      IpPrefix.valueOf(jsonNode.path(IPPREFIX).asText()),
+                      IpAddress.valueOf(jsonNode.path(NEXTHOP).asText())));
+            } catch (IllegalArgumentException e) {
+                log.warn("simple fabric network config parse error; skip: {}", jsonNode);
+            }
+        });
+
+        return routes;
+    }
+
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricEvent.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricEvent.java
new file mode 100644
index 0000000..cac1276
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricEvent.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+import org.onosproject.event.AbstractEvent;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Describes an interface event.
+ */
+public class SimpleFabricEvent extends AbstractEvent<SimpleFabricEvent.Type, String> {
+
+    public enum Type {
+        SIMPLE_FABRIC_UPDATED,  /* Indicates topology info has been updated. */
+        SIMPLE_FABRIC_FLUSH,    /* Indicates flush triggered. */
+        SIMPLE_FABRIC_IDLE,     /* Indicates idle check. */
+        SIMPLE_FABRIC_DUMP      /* Indicates to dump info on the subject to output stream. */
+    }
+
+    private PrintStream printStream;  // output stream for SIMPLE_FABRIC_DUMP
+
+    /**
+     * Creates an interface event with type and subject.
+     *
+     * @param type event type
+     * @param subject subject for dump event or dummy
+     */
+    public SimpleFabricEvent(Type type, String subject) {
+        super(type, subject);   /* subject is dummy */
+    }
+
+    /**
+     * Creates an interface event with type, subject and output stream for dump.
+     *
+     * @param type event type
+     * @param subject subject for dump event
+     * @param out output stream to dump out
+     */
+    public SimpleFabricEvent(Type type, String subject, OutputStream out) {
+        super(type, subject);   /* subject is dummy */
+        printStream = new PrintStream(out, true);
+    }
+
+    public PrintStream out() {
+        return printStream;
+    }
+
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java
new file mode 100644
index 0000000..41f0253
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+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.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.ResourceGroup;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.constraint.EncapsulationConstraint;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+/**
+ * An implementation of L2NetworkOperationService.
+ * Handles the execution order of the L2 Network operations generated by the
+ * application.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricL2Forward {
+
+    public static final String BROADCAST = "BCAST";
+    public static final String UNICAST = "UNI";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    protected ApplicationId l2ForwardAppId;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected SimpleFabricService simpleFabric;
+
+    public static final ImmutableList<Constraint> L2NETWORK_CONSTRAINTS =
+            ImmutableList.of(new PartialFailureConstraint());
+
+    private Map<Key, SinglePointToMultiPointIntent> bctIntentsMap = Maps.newConcurrentMap();
+    private Map<Key, MultiPointToSinglePointIntent> uniIntentsMap = Maps.newConcurrentMap();
+    private Set<Key> toBePurgedIntentKeys = new HashSet<>();
+
+    private final InternalSimpleFabricListener simpleFabricListener = new InternalSimpleFabricListener();
+
+    @Activate
+    public void activate() {
+        l2ForwardAppId = coreService.registerApplication(simpleFabric.L2FORWARD_APP_ID);
+        log.info("simple fabric l2 forwaring starting with l2net app id {}", l2ForwardAppId.toString());
+
+        simpleFabric.addListener(simpleFabricListener);
+
+        refresh();
+        checkIntentsPurge();
+
+        log.info("simple fabric l2forward started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("simple fabric l2forward stopping");
+
+        simpleFabric.removeListener(simpleFabricListener);
+
+        for (Intent intent : bctIntentsMap.values()) {
+            intentService.withdraw(intent);
+            toBePurgedIntentKeys.add(intent.key());
+        }
+        for (Intent intent : uniIntentsMap.values()) {
+            intentService.withdraw(intent);
+            toBePurgedIntentKeys.add(intent.key());
+        }
+        for (Key key : toBePurgedIntentKeys) {
+            Intent intentToPurge = intentService.getIntent(key);
+            if (intentToPurge != null) {
+                intentService.purge(intentToPurge);
+            }
+        }
+
+        // do not set clear for switch compatibility
+        //bctIntentsMap.clear();
+        //uniIntentsMap.clear();
+
+        log.info("simple fabric l2forward stopped");
+    }
+
+    private void refresh() {
+        log.debug("simple fabric l2forward refresh");
+
+        Map<Key, SinglePointToMultiPointIntent> newBctIntentsMap = Maps.newConcurrentMap();
+        Map<Key, MultiPointToSinglePointIntent> newUniIntentsMap = Maps.newConcurrentMap();
+
+        for (L2Network l2Network : simpleFabric.getL2Networks()) {
+            // scans all l2network regardless of dirty flag
+            // if l2Network.l2Forward == false or number of interfaces() < 2, no Intents generated
+            for (SinglePointToMultiPointIntent intent : buildBrcIntents(l2Network)) {
+                newBctIntentsMap.put(intent.key(), intent);
+            }
+            for (MultiPointToSinglePointIntent intent : buildUniIntents(l2Network, hostsFromL2Network(l2Network))) {
+                newUniIntentsMap.put(intent.key(), intent);
+            }
+            if (l2Network.dirty()) {
+                l2Network.setDirty(false);
+            }
+        }
+
+        boolean bctUpdated = false;
+        for (SinglePointToMultiPointIntent intent : bctIntentsMap.values()) {
+            SinglePointToMultiPointIntent newIntent = newBctIntentsMap.get(intent.key());
+            if (newIntent == null) {
+                log.info("simple fabric l2forward withdraw broadcast intent: {}", intent.key().toString());
+                toBePurgedIntentKeys.add(intent.key());
+                intentService.withdraw(intent);
+                bctUpdated = true;
+            }
+        }
+        for (SinglePointToMultiPointIntent intent : newBctIntentsMap.values()) {
+            SinglePointToMultiPointIntent oldIntent = bctIntentsMap.get(intent.key());
+            if (oldIntent == null ||
+                    !oldIntent.filteredEgressPoints().equals(intent.filteredEgressPoints()) ||
+                    !oldIntent.filteredIngressPoint().equals(intent.filteredIngressPoint()) ||
+                    !oldIntent.selector().equals(intent.selector()) ||
+                    !oldIntent.treatment().equals(intent.treatment()) ||
+                    !oldIntent.constraints().equals(intent.constraints())) {
+                log.info("simple fabric l2forward submit broadcast intent: {}", intent.key().toString());
+                toBePurgedIntentKeys.remove(intent.key());
+                intentService.submit(intent);
+                bctUpdated = true;
+            }
+        }
+
+        boolean uniUpdated = false;
+        for (MultiPointToSinglePointIntent intent : uniIntentsMap.values()) {
+            MultiPointToSinglePointIntent newIntent = newUniIntentsMap.get(intent.key());
+            if (newIntent == null) {
+                log.info("simple fabric l2forward withdraw unicast intent: {}", intent.key().toString());
+                toBePurgedIntentKeys.add(intent.key());
+                intentService.withdraw(intent);
+                uniUpdated = true;
+            }
+        }
+        for (MultiPointToSinglePointIntent intent : newUniIntentsMap.values()) {
+            MultiPointToSinglePointIntent oldIntent = uniIntentsMap.get(intent.key());
+            if (oldIntent == null ||
+                    !oldIntent.filteredEgressPoint().equals(intent.filteredEgressPoint()) ||
+                    !oldIntent.filteredIngressPoints().equals(intent.filteredIngressPoints()) ||
+                    !oldIntent.selector().equals(intent.selector()) ||
+                    !oldIntent.treatment().equals(intent.treatment()) ||
+                    !oldIntent.constraints().equals(intent.constraints())) {
+                log.info("simple fabric l2forward submit unicast intent: {}", intent.key().toString());
+                toBePurgedIntentKeys.remove(intent.key());
+                intentService.submit(intent);
+                uniUpdated = true;
+            }
+        }
+
+        if (bctUpdated) {
+            bctIntentsMap = newBctIntentsMap;
+        }
+        if (uniUpdated) {
+            uniIntentsMap = newUniIntentsMap;
+        }
+    }
+
+    private void checkIntentsPurge() {
+        // check intents to be purge
+        if (!toBePurgedIntentKeys.isEmpty()) {
+            Set<Key> purgedKeys = new HashSet<>();
+            for (Key key : toBePurgedIntentKeys) {
+                Intent intentToPurge = intentService.getIntent(key);
+                if (intentToPurge == null) {
+                    log.info("simple fabric l2forward purged intent: key={}", key.toString());
+                    purgedKeys.add(key);
+                } else {
+                    switch (intentService.getIntentState(key)) {
+                    case FAILED:
+                    case WITHDRAWN:
+                        log.info("simple fabric l2forward try to purge intent: key={}", key.toString());
+                        intentService.purge(intentToPurge);
+                        break;
+                    case INSTALL_REQ:
+                    case INSTALLED:
+                    case INSTALLING:
+                    case RECOMPILING:
+                    case COMPILING:
+                        log.warn("simple fabric l2forward withdraw intent to purge: key={}", key);
+                        intentService.withdraw(intentToPurge);
+                        break;
+                    case WITHDRAW_REQ:
+                    case WITHDRAWING:
+                    case PURGE_REQ:
+                    case CORRUPT:
+                    default:
+                        // no action
+                        break;
+                    }
+                }
+            }
+            toBePurgedIntentKeys.removeAll(purgedKeys);
+        }
+    }
+
+    // Generates Unicast Intents and broadcast Intents for the L2 Network.
+
+    private Set<Intent> generateL2NetworkIntents(L2Network l2Network) {
+        return new ImmutableSet.Builder<Intent>()
+            .addAll(buildBrcIntents(l2Network))
+            .addAll(buildUniIntents(l2Network, hostsFromL2Network(l2Network)))
+            .build();
+    }
+
+    // Build Boadcast Intents for a L2 Network.
+    private Set<SinglePointToMultiPointIntent> buildBrcIntents(L2Network l2Network) {
+        Set<Interface> interfaces = l2Network.interfaces();
+        if (!l2Network.l2Forward() || interfaces.size() < 2) {
+            return ImmutableSet.of();
+        }
+        Set<SinglePointToMultiPointIntent> brcIntents = Sets.newHashSet();
+        ResourceGroup resourceGroup = ResourceGroup.of(l2Network.name());
+
+        // Generates broadcast Intents from any network interface to other
+        // network interface from the L2 Network.
+        interfaces
+            .forEach(src -> {
+            FilteredConnectPoint srcFcp = buildFilteredConnectedPoint(src);
+            Set<FilteredConnectPoint> dstFcps = interfaces.stream()
+                    .filter(iface -> !iface.equals(src))
+                    .map(this::buildFilteredConnectedPoint)
+                    .collect(Collectors.toSet());
+            Key key = buildKey(l2Network.name(), "BCAST", srcFcp.connectPoint(), MacAddress.BROADCAST);
+            TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchEthDst(MacAddress.BROADCAST)
+                    .build();
+            SinglePointToMultiPointIntent.Builder intentBuilder = SinglePointToMultiPointIntent.builder()
+                    .appId(l2ForwardAppId)
+                    .key(key)
+                    .selector(selector)
+                    .filteredIngressPoint(srcFcp)
+                    .filteredEgressPoints(dstFcps)
+                    .constraints(buildConstraints(L2NETWORK_CONSTRAINTS, l2Network.encapsulation()))
+                    .priority(SimpleFabricService.PRI_L2NETWORK_BROADCAST)
+                    .resourceGroup(resourceGroup);
+            brcIntents.add(intentBuilder.build());
+        });
+        return brcIntents;
+    }
+
+    // Builds unicast Intents for a L2 Network.
+    private Set<MultiPointToSinglePointIntent> buildUniIntents(L2Network l2Network, Set<Host> hosts) {
+        Set<Interface> interfaces = l2Network.interfaces();
+        if (!l2Network.l2Forward() || interfaces.size() < 2) {
+            return ImmutableSet.of();
+        }
+        Set<MultiPointToSinglePointIntent> uniIntents = Sets.newHashSet();
+        ResourceGroup resourceGroup = ResourceGroup.of(l2Network.name());
+        hosts.forEach(host -> {
+            FilteredConnectPoint hostFcp = buildFilteredConnectedPoint(host);
+            Set<FilteredConnectPoint> srcFcps = interfaces.stream()
+                    .map(this::buildFilteredConnectedPoint)
+                    .filter(fcp -> !fcp.equals(hostFcp))
+                    .collect(Collectors.toSet());
+            Key key = buildKey(l2Network.name(), "UNI", hostFcp.connectPoint(), host.mac());
+            TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchEthDst(host.mac()).build();
+            MultiPointToSinglePointIntent.Builder intentBuilder = MultiPointToSinglePointIntent.builder()
+                    .appId(l2ForwardAppId)
+                    .key(key)
+                    .selector(selector)
+                    .filteredIngressPoints(srcFcps)
+                    .filteredEgressPoint(hostFcp)
+                    .constraints(buildConstraints(L2NETWORK_CONSTRAINTS, l2Network.encapsulation()))
+                    .priority(SimpleFabricService.PRI_L2NETWORK_UNICAST)
+                    .resourceGroup(resourceGroup);
+            uniIntents.add(intentBuilder.build());
+        });
+
+        return uniIntents;
+    }
+
+    // Intent generate utilities
+
+    private Set<Host> hostsFromL2Network(L2Network l2Network) {
+        Set<Interface> interfaces = l2Network.interfaces();
+        return interfaces.stream()
+                .map(this::hostsFromInterface)
+                .flatMap(Collection::stream)
+                .collect(Collectors.toSet());
+    }
+
+    private Set<Host> hostsFromInterface(Interface iface) {
+        return hostService.getConnectedHosts(iface.connectPoint())
+                .stream()
+                .filter(host -> host.vlan().equals(iface.vlan()))
+                .collect(Collectors.toSet());
+    }
+
+    private Key buildKey(String l2NetworkName, String type, ConnectPoint cPoint, MacAddress dstMac) {
+        return Key.of(l2NetworkName + "-" + type + "-" + cPoint.toString() + "-" + dstMac, l2ForwardAppId);
+    }
+
+    private List<Constraint> buildConstraints(List<Constraint> constraints, EncapsulationType encapsulation) {
+        if (!encapsulation.equals(EncapsulationType.NONE)) {
+            List<Constraint> newConstraints = new ArrayList<>(constraints);
+            constraints.stream()
+                .filter(c -> c instanceof EncapsulationConstraint)
+                .forEach(newConstraints::remove);
+            newConstraints.add(new EncapsulationConstraint(encapsulation));
+            return ImmutableList.copyOf(newConstraints);
+        }
+        return constraints;
+    }
+
+    private FilteredConnectPoint buildFilteredConnectedPoint(Interface iface) {
+        Objects.requireNonNull(iface);
+        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
+
+        if (iface.vlan() != null && !iface.vlan().equals(VlanId.NONE)) {
+            trafficSelector.matchVlanId(iface.vlan());
+        }
+        return new FilteredConnectPoint(iface.connectPoint(), trafficSelector.build());
+    }
+
+    protected FilteredConnectPoint buildFilteredConnectedPoint(Host host) {
+        Objects.requireNonNull(host);
+        TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
+
+        if (host.vlan() != null && !host.vlan().equals(VlanId.NONE)) {
+            trafficSelector.matchVlanId(host.vlan());
+        }
+        return new FilteredConnectPoint(host.location(), trafficSelector.build());
+    }
+
+    // Dump command handler
+    private void dump(String subject, PrintStream out) {
+        if (subject == "intents") {
+            out.println("L2Forward Broadcast Intents:\n");
+            for (SinglePointToMultiPointIntent intent: bctIntentsMap.values()) {
+                out.println("    " + intent.key().toString()
+                          + ": " + intent.selector().criteria()
+                          + ", [" + intent.filteredIngressPoint().connectPoint()
+                          + "] -> " + intent.filteredEgressPoints().stream()
+                                      .map(FilteredConnectPoint::connectPoint).collect(Collectors.toSet()));
+            }
+            out.println("");
+            out.println("L2Forward Unicast Intents:\n");
+            for (MultiPointToSinglePointIntent intent: uniIntentsMap.values()) {
+                out.println("    " + intent.key().toString()
+                          + ": " + intent.selector().criteria()
+                          + ", [" + intent.filteredIngressPoints().stream()
+                                    .map(FilteredConnectPoint::connectPoint).collect(Collectors.toSet())
+                          + "] -> " + intent.filteredEgressPoint().connectPoint());
+            }
+            out.println("");
+            out.println("L2Forward Intents to Be Purged:\n");
+            for (Key key: toBePurgedIntentKeys) {
+                out.println("    " + key.toString());
+            }
+            out.println("");
+        }
+    }
+
+    // Listener
+    private class InternalSimpleFabricListener implements SimpleFabricListener {
+        @Override
+        public void event(SimpleFabricEvent event) {
+            switch (event.type()) {
+            case SIMPLE_FABRIC_UPDATED:
+                refresh();
+                checkIntentsPurge();
+                break;
+            case SIMPLE_FABRIC_IDLE:
+                refresh();
+                checkIntentsPurge();
+                break;
+            case SIMPLE_FABRIC_DUMP:
+                dump(event.subject(), event.out());
+                break;
+            default:
+                // NOTE: nothing to do on SIMPLE_FABRIC_FLUSH
+                break;
+            }
+        }
+    }
+
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricListener.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricListener.java
new file mode 100644
index 0000000..726d578
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving alarm related events.
+ */
+public interface SimpleFabricListener extends EventListener<SimpleFabricEvent> {
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java
new file mode 100644
index 0000000..2bb53c9
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+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.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborSolicitation;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.component.ComponentService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.intf.InterfaceListener;
+import org.onosproject.net.intf.InterfaceEvent;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.HashSet;
+import java.util.Collection;
+import java.util.Set;
+import java.util.Map;
+
+import static org.onosproject.simplefabric.RouteTools.createBinaryString;
+
+
+/**
+ * Reactive routing configuration manager.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleFabricManager extends ListenerRegistry<SimpleFabricEvent, SimpleFabricListener>
+        implements SimpleFabricService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ApplicationService applicationService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry registry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    // compoents to be activated within SimpleFabric
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentService componentService;
+
+    // SimpleFabric variables
+    private ApplicationId appId = null;
+
+    // l2 broadcast networks
+    private Set<L2Network> l2Networks = new HashSet<>();
+    private Set<Interface> l2NetworkInterfaces = new HashSet<>();
+
+    // Subnet table
+    private Set<IpSubnet> ipSubnets = new HashSet<>();
+    private InvertedRadixTree<IpSubnet> ip4SubnetTable =
+                 new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+    private InvertedRadixTree<IpSubnet> ip6SubnetTable =
+                 new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+
+    // Border Route table
+    private Set<Route> borderRoutes = new HashSet<>();
+    private InvertedRadixTree<Route> ip4BorderRouteTable =
+                 new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+    private InvertedRadixTree<Route> ip6BorderRouteTable =
+                 new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+
+    // VirtialGateway
+    private Map<IpAddress, MacAddress> virtualGatewayIpMacMap = Maps.newConcurrentMap();
+
+    // Refresh monitor thread
+    private Object refreshMonitor = new Object();
+    private boolean doRefresh = false;
+    private boolean doFlush = false;
+    private InternalRefreshThread refreshThread;
+
+    // Listener for Service Events
+    private final InternalNetworkConfigListener configListener = new InternalNetworkConfigListener();
+    private final InternalDeviceListener deviceListener = new InternalDeviceListener();
+    private final InternalHostListener hostListener = new InternalHostListener();
+    private final InternalInterfaceListener interfaceListener = new InternalInterfaceListener();
+
+    private ConfigFactory<ApplicationId, SimpleFabricConfig> simpleFabricConfigFactory =
+        new ConfigFactory<ApplicationId, SimpleFabricConfig>(
+                SubjectFactories.APP_SUBJECT_FACTORY,
+                SimpleFabricConfig.class, SimpleFabricConfig.KEY) {
+        @Override
+        public SimpleFabricConfig createConfig() {
+            return new SimpleFabricConfig();
+       }
+    };
+
+    @Activate
+    public void activate() {
+        log.info("simple fabric starting");
+
+        if (appId == null) {
+            appId = coreService.registerApplication(APP_ID);
+        }
+
+        // initial refresh
+        refresh();
+
+        configService.addListener(configListener);
+        registry.registerConfigFactory(simpleFabricConfigFactory);
+        deviceService.addListener(deviceListener);
+        hostService.addListener(hostListener);
+
+        componentService.activate(appId, SimpleFabricNeighbour.class.getName());
+        componentService.activate(appId, SimpleFabricReactiveRouting.class.getName());
+        if (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) {
+            componentService.activate(appId, SimpleFabricL2Forward.class.getName());
+        }
+
+        refreshThread = new InternalRefreshThread();
+        refreshThread.start();
+
+        log.info("simple fabric started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("simple fabric stopping");
+
+        componentService.deactivate(appId, SimpleFabricNeighbour.class.getName());
+        componentService.deactivate(appId, SimpleFabricReactiveRouting.class.getName());
+        if (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) {
+            componentService.deactivate(appId, SimpleFabricL2Forward.class.getName());
+        }
+
+        deviceService.removeListener(deviceListener);
+        hostService.removeListener(hostListener);
+        registry.unregisterConfigFactory(simpleFabricConfigFactory);
+        configService.removeListener(configListener);
+
+        refreshThread.stop();
+        refreshThread = null;
+
+        log.info("simple fabric stopped");
+    }
+
+    // Set up from configuration
+    // returns found dirty and refresh listners are called (true) or not (false)
+    private boolean refresh() {
+        log.debug("simple fabric refresh");
+        boolean dirty = false;
+
+        SimpleFabricConfig config = configService.getConfig(coreService.registerApplication(APP_ID),
+                                                            SimpleFabricConfig.class);
+        if (config == null) {
+            log.debug("No reactive routing config available!");
+            return false;
+        }
+
+        // l2Networks
+        Set<L2Network> newL2Networks = new HashSet<>();
+        Set<Interface> newL2NetworkInterfaces = new HashSet<>();
+        for (L2Network newL2NetworkConfig : config.getL2Networks()) {
+            L2Network newL2Network = L2Network.of(newL2NetworkConfig);
+
+            // fill up interfaces and Hosts with active port only
+            for (String ifaceName : newL2NetworkConfig.interfaceNames()) {
+                Interface iface = getInterfaceByName(ifaceName);
+                if (iface != null && deviceService.isAvailable(iface.connectPoint().deviceId())) {
+                     newL2Network.addInterface(iface);
+                     newL2NetworkInterfaces.add(iface);
+                }
+            }
+            for (Host host : hostService.getHosts()) {
+                // consider host with ip only
+                if (!host.ipAddresses().isEmpty()) {
+                    Interface iface = getAvailableDeviceHostInterface(host);
+                    if (iface != null && newL2Network.contains(iface)) {
+                        newL2Network.addHost(host);
+                    }
+                }
+            }
+            newL2Network.setDirty(true);
+
+            // update newL2Network's dirty flags if same entry already exists
+            for (L2Network prevL2Network : l2Networks) {
+                if (prevL2Network.equals(newL2Network)) {
+                    newL2Network.setDirty(prevL2Network.dirty());
+                    break;
+                }
+            }
+            newL2Networks.add(newL2Network);
+        }
+        if (!l2Networks.equals(newL2Networks)) {
+            l2Networks = newL2Networks;
+            dirty = true;
+        }
+        if (!l2NetworkInterfaces.equals(newL2NetworkInterfaces)) {
+            l2NetworkInterfaces = newL2NetworkInterfaces;
+            dirty = true;
+        }
+
+        // ipSubnets
+        Set<IpSubnet> newIpSubnets = config.ipSubnets();
+        InvertedRadixTree<IpSubnet> newIp4SubnetTable =
+                 new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+        InvertedRadixTree<IpSubnet> newIp6SubnetTable =
+                 new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+        Map<IpAddress, MacAddress> newVirtualGatewayIpMacMap = Maps.newConcurrentMap();
+        for (IpSubnet subnet : newIpSubnets) {
+            if (subnet.ipPrefix().isIp4()) {
+                newIp4SubnetTable.put(createBinaryString(subnet.ipPrefix()), subnet);
+            } else {
+                newIp6SubnetTable.put(createBinaryString(subnet.ipPrefix()), subnet);
+            }
+            newVirtualGatewayIpMacMap.put(subnet.gatewayIp(), subnet.gatewayMac());
+        }
+        if (!ipSubnets.equals(newIpSubnets)) {
+            ipSubnets = newIpSubnets;
+            ip4SubnetTable = newIp4SubnetTable;
+            ip6SubnetTable = newIp6SubnetTable;
+            dirty = true;
+        }
+        if (!virtualGatewayIpMacMap.equals(newVirtualGatewayIpMacMap)) {
+            virtualGatewayIpMacMap = newVirtualGatewayIpMacMap;
+            dirty = true;
+        }
+
+        // borderRoutes config handling
+        Set<Route> newBorderRoutes = config.borderRoutes();
+        if (!borderRoutes.equals(newBorderRoutes)) {
+            InvertedRadixTree<Route> newIp4BorderRouteTable =
+                    new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+            InvertedRadixTree<Route> newIp6BorderRouteTable =
+                    new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+            for (Route route : newBorderRoutes) {
+                if (route.prefix().isIp4()) {
+                    newIp4BorderRouteTable.put(createBinaryString(route.prefix()), route);
+                } else {
+                    newIp6BorderRouteTable.put(createBinaryString(route.prefix()), route);
+                }
+            }
+            borderRoutes = newBorderRoutes;
+            ip4BorderRouteTable = newIp4BorderRouteTable;
+            ip6BorderRouteTable = newIp6BorderRouteTable;
+            dirty = true;
+        }
+
+        // notify to SimpleFabric listeners
+        if (dirty) {
+            log.info("simple fabric refresh; notify events");
+            process(new SimpleFabricEvent(SimpleFabricEvent.Type.SIMPLE_FABRIC_UPDATED, "updated"));
+        }
+        return dirty;
+    }
+
+    private Interface getInterfaceByName(String interfaceName) {
+        Interface intf = interfaceService.getInterfaces().stream()
+                          .filter(iface -> iface.name().equals(interfaceName))
+                          .findFirst()
+                          .orElse(null);
+        if (intf == null) {
+            log.warn("simple fabric unknown interface name: {}", interfaceName);
+        }
+        return intf;
+    }
+
+    @Override
+    public ApplicationId getAppId() {
+        if (appId == null) {
+            appId = coreService.registerApplication(APP_ID);
+        }
+        return appId;
+    }
+
+    @Override
+    public Collection<L2Network> getL2Networks() {
+        return ImmutableSet.copyOf(l2Networks);
+    }
+
+    @Override
+    public Set<IpSubnet> getIpSubnets() {
+        return ImmutableSet.copyOf(ipSubnets);
+    }
+
+    @Override
+    public Set<Route> getBorderRoutes() {
+        return ImmutableSet.copyOf(borderRoutes);
+    }
+
+    @Override
+    public MacAddress getVMacForIp(IpAddress ip) {
+        return virtualGatewayIpMacMap.get(ip);
+    }
+
+    @Override
+    public boolean isVMac(MacAddress mac) {
+        return virtualGatewayIpMacMap.containsValue(mac);
+    }
+
+    @Override
+    public boolean isL2NetworkInterface(Interface intf) {
+        return l2NetworkInterfaces.contains(intf);
+    }
+
+    @Override
+    public L2Network findL2Network(ConnectPoint port, VlanId vlanId) {
+        for (L2Network l2Network : l2Networks) {
+            if (l2Network.contains(port, vlanId)) {
+                return l2Network;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public L2Network findL2Network(String name) {
+        for (L2Network l2Network : l2Networks) {
+            if (l2Network.name().equals(name)) {
+                return l2Network;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public IpSubnet findIpSubnet(IpAddress ip) {
+        Iterator<IpSubnet> it;
+        if (ip.isIp4()) {
+            it = ip4SubnetTable.getValuesForKeysPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip4Address.BIT_LENGTH))).iterator();
+        } else {
+            it = ip6SubnetTable.getValuesForKeysPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip6Address.BIT_LENGTH))) .iterator();
+        }
+        return (it.hasNext()) ? it.next() : null;
+    }
+
+    @Override
+    public Route findBorderRoute(IpAddress ip) {
+        // ASSUME: ipAddress is out of ipSubnet
+        Iterator<Route> it;
+        if (ip.isIp4()) {
+            it = ip4BorderRouteTable.getValuesForKeysPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip4Address.BIT_LENGTH))) .iterator();
+        } else {
+            it = ip6BorderRouteTable.getValuesForKeysPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip6Address.BIT_LENGTH))) .iterator();
+        }
+        return (it.hasNext()) ? it.next() : null;
+    }
+
+
+    @Override
+    public Interface getHostInterface(Host host) {
+        return interfaceService.getInterfaces().stream()
+                .filter(iface -> iface.connectPoint().equals(host.location()) &&
+                                 iface.vlan().equals(host.vlan()))
+                .findFirst()
+                .orElse(null);
+    }
+
+    private Interface getAvailableDeviceHostInterface(Host host) {
+        return interfaceService.getInterfaces().stream()
+                .filter(iface -> iface.connectPoint().equals(host.location()) &&
+                                 iface.vlan().equals(host.vlan()))
+                .filter(iface -> deviceService.isAvailable(iface.connectPoint().deviceId()))
+                .findFirst()
+                .orElse(null);
+    }
+
+    @Override
+    public boolean isIpAddressLocal(IpAddress ip) {
+        boolean result;
+        if (ip.isIp4()) {
+            return ip4SubnetTable.getValuesForKeysPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip4Address.BIT_LENGTH)))
+                     .iterator().hasNext();
+        } else {
+            return ip6SubnetTable.getValuesForKeysPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip6Address.BIT_LENGTH)))
+                     .iterator().hasNext();
+        }
+    }
+
+    @Override
+    public boolean isIpPrefixLocal(IpPrefix ipPrefix) {
+        if (ipPrefix.isIp4()) {
+            return (ip4SubnetTable.getValueForExactKey(createBinaryString(ipPrefix)) != null);
+        } else {
+            return (ip6SubnetTable.getValueForExactKey(createBinaryString(ipPrefix)) != null);
+        }
+    }
+
+    @Override
+    public boolean requestMac(IpAddress ip) {
+        IpSubnet ipSubnet = findIpSubnet(ip);
+        if (ipSubnet == null) {
+            log.warn("simple fabric request mac failed for unknown IpSubnet: {}", ip);
+            return false;
+        }
+        L2Network l2Network = findL2Network(ipSubnet.l2NetworkName());
+        if (l2Network == null) {
+            log.warn("simple fabric request mac failed for unknown l2Network name {}: {}",
+                     ipSubnet.l2NetworkName(), ip);
+            return false;
+        }
+        log.debug("simple fabric send request mac L2Network {}: {}", l2Network.name(), ip);
+        for (Interface iface : l2Network.interfaces()) {
+            Ethernet neighbourReq;
+            if (ip.isIp4()) {
+                neighbourReq = ARP.buildArpRequest(ipSubnet.gatewayMac().toBytes(),
+                                                   ipSubnet.gatewayIp().toOctets(),
+                                                   ip.toOctets(),
+                                                   iface.vlan().toShort());
+            } else {
+                byte[] soliciteIp = IPv6.getSolicitNodeAddress(ip.toOctets());
+                neighbourReq = NeighborSolicitation.buildNdpSolicit(
+                                                   ip.toOctets(),
+                                                   ipSubnet.gatewayIp().toOctets(),
+                                                   soliciteIp,
+                                                   ipSubnet.gatewayMac().toBytes(),
+                                                   IPv6.getMCastMacAddress(soliciteIp),
+                                                   iface.vlan());
+            }
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                                               .setOutput(iface.connectPoint().port()).build();
+            OutboundPacket packet = new DefaultOutboundPacket(iface.connectPoint().deviceId(),
+                                               treatment, ByteBuffer.wrap(neighbourReq.serialize()));
+            packetService.emit(packet);
+        }
+        return true;
+    }
+
+    @Override
+    public void dumpToStream(String subject, OutputStream out) {
+        SimpleFabricEvent event = new SimpleFabricEvent(SimpleFabricEvent.Type.SIMPLE_FABRIC_DUMP, subject, out);
+        dump(event.subject(), event.out());  // dump in itself
+        process(event);  // dump in sub modules
+    }
+
+    // Dump handler
+    protected void dump(String subject, PrintStream out) {
+        if (subject == "show") {
+            out.println("Static Configuration Flag:");
+            out.println("    ALLOW_ETH_ADDRESS_SELECTOR="
+                        + SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR);
+            out.println("    REACTIVE_SINGLE_TO_SINGLE="
+                        + SimpleFabricService.REACTIVE_SINGLE_TO_SINGLE);
+            out.println("    REACTIVE_ALLOW_LINK_CP="
+                        + SimpleFabricService.REACTIVE_ALLOW_LINK_CP);
+            out.println("    REACTIVE_HASHED_PATH_SELECTION="
+                        + SimpleFabricService.REACTIVE_HASHED_PATH_SELECTION);
+            out.println("    REACTIVE_MATCH_IP_PROTO="
+                        + SimpleFabricService.REACTIVE_MATCH_IP_PROTO);
+            out.println("");
+            out.println("SimpleFabricAppId:");
+            out.println("    " + getAppId());
+            out.println("");
+            out.println("l2Networks:");
+            for (L2Network l2Network : getL2Networks()) {
+                out.println("    " + l2Network);
+            }
+            out.println("");
+            out.println("ipSubnets:");
+            for (IpSubnet ipSubnet : getIpSubnets()) {
+                out.println("    " + ipSubnet);
+            }
+            out.println("");
+            out.println("borderRoutes:");
+            for (Route route : getBorderRoutes()) {
+                out.println("    " + route);
+            }
+        }
+    }
+
+    // Refresh action thread and notifier
+
+    private class InternalRefreshThread extends Thread {
+        public void run() {
+            while (true) {
+                boolean doRefreshMarked = false;
+                boolean doFlushMarked = false;
+                synchronized (refreshMonitor) {
+                    if (!doRefresh && !doFlush) {
+                        try {
+                            refreshMonitor.wait(IDLE_INTERVAL_MSEC);
+                        } catch (InterruptedException e) {
+                        }
+                    }
+                    doRefreshMarked = doRefresh;
+                    doRefresh = false;
+                    doFlushMarked = doFlush;
+                    doFlush = false;
+                }
+                if (doRefreshMarked) {
+                    try {
+                        refresh();
+                    } catch (Exception e) {
+                        log.warn("simple fabric refresh failed: exception={}", e);
+                    }
+                }
+                if (doFlushMarked) {
+                    try {
+                        log.info("simple fabric flush execute");
+                        process(new SimpleFabricEvent(SimpleFabricEvent.Type.SIMPLE_FABRIC_FLUSH, "flush"));
+                    } catch (Exception e) {
+                        log.warn("simple fabric flush failed: exception={}", e);
+                    }
+                }
+                if (!doRefreshMarked && !doFlushMarked) {
+                    try {
+                        if (!refresh()) {
+                            process(new SimpleFabricEvent(SimpleFabricEvent.Type.SIMPLE_FABRIC_IDLE, "idle"));
+                        }
+                    } catch (Exception e) {
+                        log.warn("simple fabric idle failed: exception={}", e);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void triggerRefresh() {
+        synchronized (refreshMonitor) {
+            doRefresh = true;
+            refreshMonitor.notifyAll();
+        }
+    }
+
+    @Override
+    public void triggerFlush() {
+        synchronized (refreshMonitor) {
+            doFlush = true;
+            refreshMonitor.notifyAll();
+        }
+    }
+
+    // Service Listeners
+
+    private class InternalNetworkConfigListener implements NetworkConfigListener {
+        @Override
+        public void event(NetworkConfigEvent event) {
+            switch (event.type()) {
+            case CONFIG_REGISTERED:
+            case CONFIG_UNREGISTERED:
+            case CONFIG_ADDED:
+            case CONFIG_UPDATED:
+            case CONFIG_REMOVED:
+                if (event.configClass().equals(SimpleFabricConfig.class)) {
+                    triggerRefresh();
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            switch (event.type()) {
+            case DEVICE_ADDED:
+            case DEVICE_AVAILABILITY_CHANGED:
+            case DEVICE_REMOVED:
+            case DEVICE_SUSPENDED:
+            case DEVICE_UPDATED:
+            case PORT_ADDED:
+            case PORT_REMOVED:
+            case PORT_UPDATED:
+            // case PORT_STATS_UPDATED:  IGNORED
+                triggerRefresh();
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    private class InternalHostListener implements HostListener {
+        @Override
+        public void event(HostEvent event) {
+            Host host = event.subject();
+            Host prevHost = event.prevSubject();
+            switch (event.type()) {
+            case HOST_MOVED:
+            case HOST_REMOVED:
+            case HOST_ADDED:
+            case HOST_UPDATED:
+                triggerRefresh();
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    private class InternalInterfaceListener implements InterfaceListener {
+        @Override
+        public void event(InterfaceEvent event) {
+            Interface iface = event.subject();
+            Interface prevIface = event.prevSubject();
+            switch (event.type()) {
+            case INTERFACE_ADDED:
+            case INTERFACE_REMOVED:
+            case INTERFACE_UPDATED:
+                triggerRefresh();
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java
new file mode 100644
index 0000000..9d0b3fc
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+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.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.neighbour.NeighbourMessageContext;
+import org.onosproject.net.neighbour.NeighbourMessageHandler;
+import org.onosproject.net.neighbour.NeighbourResolutionService;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+
+/**
+ * Handles neighbour messages for on behalf of the L2 Network application. Handlers
+ * will be changed automatically by interface or network configuration events.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricNeighbour {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    protected ApplicationId appId;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NeighbourResolutionService neighbourService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected SimpleFabricService simpleFabric;
+
+    private final InternalSimpleFabricListener simpleFabricListener =
+            new InternalSimpleFabricListener();
+
+    private L2NetworkNeighbourMessageHandler neighbourHandler =
+            new L2NetworkNeighbourMessageHandler();
+
+    private Set<Interface> monitoredInterfaces = new HashSet<>();
+
+    @Activate
+    public void activate() {
+        appId = simpleFabric.getAppId();
+        simpleFabric.addListener(simpleFabricListener);
+        refresh();
+        log.info("simple fabric neighbour started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        simpleFabric.removeListener(simpleFabricListener);
+        unregister();
+        monitoredInterfaces.clear();
+        log.info("simple fabric neighbour stoped");
+    }
+
+    /**
+     * Registers neighbour handler to all available interfaces.
+     */
+    protected void refresh() {
+        Set<Interface> interfaces = interfaceService.getInterfaces();
+        // check for new interfaces
+        for (Interface intf : interfaces) {
+            if (!monitoredInterfaces.contains(intf) && simpleFabric.isL2NetworkInterface(intf)) {
+               log.info("simple fabric neighbour register handler: {}", intf);
+               neighbourService.registerNeighbourHandler(intf, neighbourHandler, appId);
+               monitoredInterfaces.add(intf);
+            } else {
+               log.debug("simple fabric neighobur unknown interface: {}", intf);
+            }
+        }
+        // check for removed interfaces
+        Set<Interface> monitoredInterfacesToBeRemoved = new HashSet<>();
+        for (Interface intf : monitoredInterfaces) {
+            if (!interfaces.contains(intf)) {
+               log.info("simple fabric neighbour unregister handler: {}", intf);
+               neighbourService.unregisterNeighbourHandler(intf, neighbourHandler, appId);
+               monitoredInterfacesToBeRemoved.add(intf);
+            }
+        }
+        for (Interface intf : monitoredInterfacesToBeRemoved) {
+            monitoredInterfaces.remove(intf);
+        }
+    }
+
+    /**
+     * Unregisters neighbour handler to all available interfaces.
+     */
+    protected void unregister() {
+        log.info("simple fabric neighbour unregister handler");
+        neighbourService.unregisterNeighbourHandlers(appId);
+    }
+
+    /**
+     * Handles request messages.
+     *
+     * @param context the message context
+     */
+    protected void handleRequest(NeighbourMessageContext context) {
+        MacAddress mac = simpleFabric.getVMacForIp(context.target());
+        if (mac != null) {
+            log.trace("simple fabric neightbour request on virtualGatewayAddress {}; response to {} {} mac={}",
+                      context.target(), context.inPort(), context.vlan(), mac);
+            context.reply(mac);
+            return;
+        }
+        // else forward to corresponding host
+
+        L2Network l2Network = simpleFabric.findL2Network(context.inPort(), context.vlan());
+        if (l2Network != null) {
+            int numForwards = 0;
+            if (!context.dstMac().isBroadcast() && !context.dstMac().isMulticast()) {
+                for (Host host : hostService.getHostsByMac(context.dstMac())) {
+                    log.trace("simple fabric neightbour request forward unicast to {}", host.location());
+                    context.forward(host.location());  // ASSUME: vlan is same
+                    // TODO: may need to check host.location().time()
+                    numForwards++;
+                }
+                if (numForwards > 0) {
+                    return;
+                }
+            }
+            // else do broadcast to all host in the same l2 network
+            log.trace("simple fabric neightbour request forward broadcast: {} {}",
+                     context.inPort(), context.vlan());
+            for (Interface iface : l2Network.interfaces()) {
+                if (!context.inPort().equals(iface.connectPoint())) {
+                    log.trace("simple fabric forward neighbour request broadcast to {}", iface);
+                    context.forward(iface);
+                }
+            }
+        } else {
+            log.warn("simple fabric neightbour request drop: {} {}",
+                     context.inPort(), context.vlan());
+            context.drop();
+        }
+    }
+
+    /**
+     * Handles reply messages between VLAN tagged interfaces.
+     *
+     * @param context the message context
+     * @param hostService the host service
+     */
+    protected void handleReply(NeighbourMessageContext context,
+                               HostService hostService) {
+        // Find target L2 Network, then reply to the host
+        L2Network l2Network = simpleFabric.findL2Network(context.inPort(), context.vlan());
+        if (l2Network != null) {
+            // TODO: need to check and update simpleFabric.L2Network
+            MacAddress mac = simpleFabric.getVMacForIp(context.target());
+            if (mac != null) {
+                log.trace("simple fabric neightbour response message to virtual gateway; drop: {} {} target={}",
+                          context.inPort(), context.vlan(), context.target());
+                context.drop();
+            } else {
+                // forward reply to the hosts of the dstMac
+                Set<Host> hosts = hostService.getHostsByMac(context.dstMac());
+                log.trace("simple fabric neightbour response message forward: {} {} target={} -> {}",
+                          context.inPort(), context.vlan(), context.target(), hosts);
+                hosts.stream()
+                        .map(host -> simpleFabric.getHostInterface(host))
+                        .filter(Objects::nonNull)
+                        .forEach(context::forward);
+            }
+        } else {
+            // this might be happened when we remove an interface from L2 Network
+            // just ignore this message
+            log.warn("simple fabric neightbour response message drop for unknown l2Network: {} {}",
+                     context.inPort(), context.vlan());
+            context.drop();
+        }
+    }
+
+    private class L2NetworkNeighbourMessageHandler implements NeighbourMessageHandler {
+        @Override
+        public void handleMessage(NeighbourMessageContext context,
+                                  HostService hostService) {
+            switch (context.type()) {
+                case REQUEST:
+                    handleRequest(context);
+                    break;
+                case REPLY:
+                    handleReply(context, hostService);
+                    break;
+                default:
+                    log.warn("simple fabric neightor unknown context type: {}", context.type());
+                    break;
+            }
+        }
+    }
+
+    private class InternalSimpleFabricListener implements SimpleFabricListener {
+        @Override
+        public void event(SimpleFabricEvent event) {
+            switch (event.type()) {
+            case SIMPLE_FABRIC_UPDATED:
+                refresh();
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java
new file mode 100644
index 0000000..10148c8
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java
@@ -0,0 +1,977 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+import com.google.common.collect.ImmutableList;
+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.Ethernet;
+import org.onlab.packet.ICMP;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+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.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.constraint.EncapsulationConstraint;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.net.intent.constraint.HashedPathSelectionConstraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.Port;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * SimpleFabricReactiveRouting handles L3 Reactive Routing.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricReactiveRouting {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private ApplicationId reactiveAppId;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService linkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected SimpleFabricService simpleFabric;
+
+    private ImmutableList<Constraint> reactiveConstraints
+            = ImmutableList.of(new PartialFailureConstraint());
+            //= ImmutableList.of();
+            // NOTE: SHOULD NOT use HashedPathSelectionConstraint
+            //       for unpredictable srcCp of Link appears as reactive packet traffic
+
+    private Set<FlowRule> interceptFlowRules = new HashSet<>();
+    private Set<Key> toBePurgedIntentKeys = new HashSet<>();
+            // NOTE: manage purged intents by key for intentService.getIntent() supports key only
+
+    private final InternalSimpleFabricListener simpleFabricListener = new InternalSimpleFabricListener();
+    private ReactiveRoutingProcessor processor = new ReactiveRoutingProcessor();
+
+    @Activate
+    public void activate() {
+        reactiveAppId = coreService.registerApplication(simpleFabric.REACTIVE_APP_ID);
+        log.info("simple fabric reactive routing starting with app id {}", reactiveAppId.toString());
+
+        // NOTE: may not clear at init for MIGHT generate pending_remove garbages
+        //       use flush event from simple fabric cli command
+
+        if (simpleFabric.REACTIVE_HASHED_PATH_SELECTION) {
+            reactiveConstraints = ImmutableList.of(new PartialFailureConstraint(),
+                                                   new HashedPathSelectionConstraint());
+        } else {
+            reactiveConstraints = ImmutableList.of(new PartialFailureConstraint());
+        }
+
+        processor = new ReactiveRoutingProcessor();
+        packetService.addProcessor(processor, PacketProcessor.director(2));
+        simpleFabric.addListener(simpleFabricListener);
+
+        registerIntercepts();
+        refreshIntercepts();
+
+        log.info("simple fabric reactive routing started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("simple fabric reactive routing stopping");
+
+        packetService.removeProcessor(processor);
+        simpleFabric.removeListener(simpleFabricListener);
+
+        withdrawIntercepts();
+
+        // NOTE: may not clear at init for MIGHT generate pending_remove garbages
+        //       use flush event from simple fabric cli command
+
+        toBePurgedIntentKeys.clear();
+
+        flowRuleService.removeFlowRulesById(reactiveAppId);
+
+        processor = null;
+
+        log.info("simple fabric reactive routing stopped");
+    }
+
+    /**
+     * Request packet in via the PacketService.
+     */
+    private void registerIntercepts() {
+        // register default intercepts on packetService for broder routing intercepts
+
+        packetService.requestPackets(
+            DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV4).build(),
+            PacketPriority.REACTIVE, reactiveAppId);
+
+        if (simpleFabric.ALLOW_IPV6) {
+            packetService.requestPackets(
+                DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV6).build(),
+                PacketPriority.REACTIVE, reactiveAppId);
+        }
+
+        log.info("simple fabric reactive routing ip packet intercepts started");
+    }
+
+    /**
+     * Cancel request for packet in via PacketService.
+     */
+    private void withdrawIntercepts() {
+        // unregister default intercepts on packetService
+
+        packetService.cancelPackets(
+            DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV4).build(),
+            PacketPriority.REACTIVE, reactiveAppId);
+
+        if (simpleFabric.ALLOW_IPV6) {
+            packetService.cancelPackets(
+                DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV6).build(),
+                PacketPriority.REACTIVE, reactiveAppId);
+        }
+
+        log.info("simple fabric reactive routing ip packet intercepts stopped");
+    }
+
+    /**
+     * Refresh device flow rules for reative intercepts on local ipSubnets.
+     */
+    private void refreshIntercepts() {
+        Set<FlowRule> newInterceptFlowRules = new HashSet<>();
+        for (Device device : deviceService.getAvailableDevices()) {
+            for (IpSubnet subnet : simpleFabric.getIpSubnets()) {
+                newInterceptFlowRules.add(generateInterceptFlowRule(true, device.id(), subnet.ipPrefix()));
+                // check if this devices has the ipSubnet, then add ip broadcast flue rule
+                L2Network l2Network = simpleFabric.findL2Network(subnet.l2NetworkName());
+                if (l2Network != null && l2Network.contains(device.id())) {
+                    newInterceptFlowRules.add(generateLocalSubnetIpBctFlowRule(device.id(), subnet.ipPrefix()));
+                }
+                // JUST FOR FLOW RULE TEST ONLY
+                //newInterceptFlowRules.add(generateTestFlowRule(device.id(), subnet.ipPrefix()));
+            }
+            for (Route route : simpleFabric.getBorderRoutes()) {
+                newInterceptFlowRules.add(generateInterceptFlowRule(false, device.id(), route.prefix()));
+            }
+        }
+
+        if (!newInterceptFlowRules.equals(interceptFlowRules)) {
+            // NOTE: DO NOT REMOVE INTERCEPT FLOW RULES FOR FAILED DEVICE FLOW UPDATE MIGHT BE BLOCKED
+            /*
+            interceptFlowRules.stream()
+                .filter(rule -> !newInterceptFlowRules.contains(rule))
+                .forEach(rule -> {
+                    flowRuleService.removeFlowRules(rule);
+                    log.info("simple fabric reactive routing remove intercept flow rule: {}", rule);
+                });
+            */
+            newInterceptFlowRules.stream()
+                .filter(rule -> !interceptFlowRules.contains(rule))
+                .forEach(rule -> {
+                    flowRuleService.applyFlowRules(rule);
+                    log.info("simple fabric reactive routing apply intercept flow rule: {}", rule);
+                });
+            interceptFlowRules = newInterceptFlowRules;
+        }
+    }
+
+    private FlowRule generateInterceptFlowRule(boolean isLocalSubnet, DeviceId deviceId, IpPrefix prefix) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        if (prefix.isIp4()) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+            if (prefix.prefixLength() > 0) {
+                selector.matchIPDst(prefix);
+            }
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+            if (prefix.prefixLength() > 0) {
+                selector.matchIPv6Dst(prefix);
+            }
+        }
+        FlowRule rule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withPriority(reactivePriority(false, isLocalSubnet, prefix.prefixLength()))
+                .withSelector(selector.build())
+                .withTreatment(DefaultTrafficTreatment.builder().punt().build())
+                .fromApp(reactiveAppId)
+                .makePermanent()
+                .forTable(0).build();
+        return rule;
+    }
+
+    private FlowRule generateLocalSubnetIpBctFlowRule(DeviceId deviceId, IpPrefix prefix) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        IpPrefix bctPrefix;
+        if (prefix.isIp4()) {
+            bctPrefix = Ip4Prefix.valueOf(prefix.getIp4Prefix().address().toInt() |
+                                              ~Ip4Address.makeMaskPrefix(prefix.prefixLength()).toInt(),
+                                          Ip4Address.BIT_LENGTH);
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+            selector.matchIPDst(bctPrefix);
+        } else {
+            byte[] p = prefix.getIp6Prefix().address().toOctets();
+            byte[] m = Ip6Address.makeMaskPrefix(prefix.prefixLength()).toOctets();
+            for (int i = 0; i < p.length; i++) {
+                 p[i] |= ~m[i];
+            }
+            bctPrefix = Ip6Prefix.valueOf(p, Ip6Address.BIT_LENGTH);
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+            selector.matchIPv6Dst(bctPrefix);
+        }
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        Set<ConnectPoint> newEgressPoints = new HashSet<>();
+        for (Port port : deviceService.getPorts(deviceId)) {
+            treatment.setOutput(port.number());
+        }
+        FlowRule rule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withPriority(reactivePriority(true, true, bctPrefix.prefixLength()))
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .fromApp(reactiveAppId)
+                .makePermanent()
+                .forTable(0).build();
+        return rule;
+    }
+
+    /**
+     * Refresh routes by examining network resource status.
+     */
+    private void refreshRouteIntents() {
+        for (Intent entry : intentService.getIntents()) {
+            if (!reactiveAppId.equals(entry.appId())) {
+                continue;
+            }
+
+            MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
+
+            if (!intentService.isLocal(intent.key())) {
+                if (toBePurgedIntentKeys.contains(intent.key())) {
+                    toBePurgedIntentKeys.remove(intent.key());  // clear non local intent
+                }
+                continue;
+            }
+
+            try {
+                switch (intentService.getIntentState(intent.key())) {
+                //case FAILED:   // failed intent is not auto removed
+                case WITHDRAWN:
+                    log.warn("intent found failed or withdrawn; "
+                             +  "remove and try to purge intent: key={}", intent.key());
+                    // purge intents here without withdraw
+                    intentService.purge(intentService.getIntent(intent.key()));
+                    toBePurgedIntentKeys.add(intent.key());
+                    continue;
+                default: // no action
+                    break;
+                }
+            } catch (Exception e) {
+                log.warn("intent status lookup failed: error={}", e);
+                continue;  // this intent seems invalid; no action
+            }
+
+            // dummy loop to break on remove cases
+            if (!deviceService.isAvailable(intent.egressPoint().deviceId())) {
+                log.info("refresh route intents; remove intent for no device: key={}", intent.key());
+                intentService.withdraw(intentService.getIntent(intent.key()));
+                toBePurgedIntentKeys.add(intent.key());
+                continue;
+            }
+            if (!(simpleFabric.findL2Network(intent.egressPoint(), VlanId.NONE) != null ||
+                  (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
+                   !linkService.getEgressLinks(intent.egressPoint()).isEmpty()))) {
+                log.info("refresh route intents; remove intent for egress point not available: key={}", intent.key());
+                intentService.withdraw(intentService.getIntent(intent.key()));
+                toBePurgedIntentKeys.add(intent.key());
+                continue;
+            }
+
+            // MAY NEED TO CHECK: intent.egressPoint and intent.treatment's dstMac is valid against hosts
+            if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && !simpleFabric.REACTIVE_ALLOW_LINK_CP) {
+                // single path intent only; no need to check ingress points
+                continue;
+            }
+
+            Set<ConnectPoint> newIngressPoints = new HashSet<>();
+            boolean ingressPointChanged = false;
+            for (ConnectPoint cp : intent.ingressPoints()) {
+                if (deviceService.isAvailable(cp.deviceId()) &&
+                    (simpleFabric.findL2Network(cp, VlanId.NONE) != null ||
+                     (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
+                      !linkService.getIngressLinks(cp).isEmpty()))) {
+                    newIngressPoints.add(cp);
+                } else {
+                    log.info("refresh route ingress cp of "
+                             + "not in 2Networks nor links: {}", cp);
+                    ingressPointChanged = true;
+                }
+            }
+            if (newIngressPoints.isEmpty()) {
+                log.info("refresh route intents; "
+                          + "remove intent for no ingress nor egress point available: key={}", intent.key());
+                intentService.withdraw(intentService.getIntent(intent.key()));
+                toBePurgedIntentKeys.add(intent.key());
+                continue;
+            }
+            // update ingress points
+            if (ingressPointChanged) {
+                MultiPointToSinglePointIntent updatedIntent =
+                    MultiPointToSinglePointIntent.builder()
+                        .appId(reactiveAppId)
+                        .key(intent.key())
+                        .selector(intent.selector())
+                        .treatment(intent.treatment())
+                        .ingressPoints(newIngressPoints)
+                        .egressPoint(intent.egressPoint())
+                        .priority(intent.priority())
+                        .constraints(intent.constraints())
+                        .build();
+                log.info("refresh route update intent: key={} updatedIntent={}",
+                        intent.key(), updatedIntent);
+                toBePurgedIntentKeys.remove(intent.key());   // may remove from old purged entry
+                intentService.submit(updatedIntent);
+            }
+        }
+    }
+
+    private void checkIntentsPurge() {
+        // check intents to be purge
+        if (!toBePurgedIntentKeys.isEmpty()) {
+            Set<Key> removeKeys = new HashSet<>();
+            for (Key key : toBePurgedIntentKeys) {
+                if (!intentService.isLocal(key)) {
+                    removeKeys.add(key);
+                    continue;
+                }
+                Intent intentToPurge = intentService.getIntent(key);
+                if (intentToPurge == null) {
+                    log.info("purged intent: key={}", key);
+                    removeKeys.add(key);
+                } else {
+                    switch (intentService.getIntentState(key)) {
+                    // case FAILED:  // not auto removed
+                    case WITHDRAWN:
+                        log.info("try to purge intent: key={}", key);
+                        intentService.purge(intentToPurge);
+                        break;
+                    case INSTALL_REQ:
+                    case INSTALLED:
+                    case INSTALLING:
+                    case RECOMPILING:
+                    case COMPILING:
+                        log.warn("not to purge for active intent: key={}", key);
+                        removeKeys.add(key);
+                        break;
+                    case WITHDRAW_REQ:
+                    case WITHDRAWING:
+                    case PURGE_REQ:
+                    case CORRUPT:
+                    default:
+                        // no action
+                        break;
+                    }
+                }
+            }
+            toBePurgedIntentKeys.removeAll(removeKeys);
+        }
+    }
+
+    public void withdrawAllReactiveIntents() {
+        // check all intents of this app
+        // NOTE: cli calls are handling within the cli called node only; so should not user inents.isLocal()
+        Set<Intent> myIntents = new HashSet<>();
+        for (Intent intent : intentService.getIntents()) {
+            if (reactiveAppId.equals(intent.appId())) {
+                myIntents.add(intent);
+            }
+        }
+        // withdraw all my intents
+        for (Intent intent : myIntents) {
+            switch (intentService.getIntentState(intent.key())) {
+            case FAILED:
+                intentService.purge(intent);
+                toBePurgedIntentKeys.add(intent.key());
+                break;
+            case WITHDRAWN:
+                intentService.purge(intent);
+                toBePurgedIntentKeys.add(intent.key());
+                break;
+            case INSTALL_REQ:
+            case INSTALLED:
+            case INSTALLING:
+            case RECOMPILING:
+            case COMPILING:
+                intentService.withdraw(intent);
+                toBePurgedIntentKeys.add(intent.key());
+                break;
+            case WITHDRAW_REQ:
+            case WITHDRAWING:
+                toBePurgedIntentKeys.add(intent.key());
+                break;
+            case PURGE_REQ:
+            case CORRUPT:
+            default:
+                // no action
+                break;
+            }
+        }
+    }
+
+    /**
+     * Reactive Packet Handling.
+     */
+    private class ReactiveRoutingProcessor implements PacketProcessor {
+        @Override
+        public void process(PacketContext context) {
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethPkt = pkt.parsed();
+            if (ethPkt == null) {
+                return;
+            }
+            ConnectPoint srcCp = pkt.receivedFrom();
+            IpAddress srcIp;
+            IpAddress dstIp;
+            byte ipProto = 0;  /* 0 or tcp, udp */
+
+            switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
+            case IPV4:
+                IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
+                srcIp = IpAddress.valueOf(ipv4Packet.getSourceAddress());
+                dstIp = IpAddress.valueOf(ipv4Packet.getDestinationAddress());
+                ipProto = ipv4Packet.getProtocol();
+                break;
+            case IPV6:
+                IPv6 ipv6Packet = (IPv6) ethPkt.getPayload();
+                srcIp = IpAddress.valueOf(IpAddress.Version.INET6, ipv6Packet.getSourceAddress());
+                dstIp = IpAddress.valueOf(IpAddress.Version.INET6, ipv6Packet.getDestinationAddress());
+                ipProto = ipv6Packet.getNextHeader();
+                break;
+            default:
+                return;  // ignore unknow ether type packets
+            }
+            if (ipProto != 6 && ipProto != 17) {
+                ipProto = 0;  /* handle special for TCP and UDP only */
+            }
+
+            if (!checkVirtualGatewayIpPacket(pkt, srcIp, dstIp)) {
+                ipPacketReactiveProcessor(context, ethPkt, srcCp, srcIp, dstIp, ipProto);
+                // TODO: add ReactiveRouting for dstIp to srcIp with discovered egressCp as srcCp
+            }
+        }
+    }
+
+    /**
+     * handle Packet with dstIp=virtualGatewayIpAddresses.
+     * returns true(handled) or false(not for virtual gateway)
+     */
+    private boolean checkVirtualGatewayIpPacket(InboundPacket pkt, IpAddress srcIp, IpAddress dstIp) {
+        Ethernet ethPkt = pkt.parsed();  // assume valid
+
+        MacAddress mac = simpleFabric.getVMacForIp(dstIp);
+        if (mac == null || !ethPkt.getDestinationMAC().equals(mac)) {
+            return false;
+        } else if (dstIp.isIp4()) {
+            IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
+            if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_ICMP) {
+                ICMP icmpPacket = (ICMP) ipv4Packet.getPayload();
+
+                if (icmpPacket.getIcmpType() == ICMP.TYPE_ECHO_REQUEST) {
+                    log.info("IPV4 ICMP ECHO request to virtual gateway: "
+                              + "srcIp={} dstIp={} proto={}", srcIp, dstIp, ipv4Packet.getProtocol());
+                    TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                                .setOutput(pkt.receivedFrom().port()).build();
+                    OutboundPacket packet =
+                        new DefaultOutboundPacket(pkt.receivedFrom().deviceId(), treatment,
+                                ByteBuffer.wrap(icmpPacket.buildIcmpReply(pkt.parsed()).serialize()));
+                    packetService.emit(packet);
+                    return true;
+                }
+            }
+            log.warn("IPV4 packet to virtual gateway dropped: "
+                     + "srcIp={} dstIp={} proto={}", srcIp, dstIp, ipv4Packet.getProtocol());
+            return true;
+
+        } else if (dstIp.isIp6()) {
+            // TODO: not tested yet (2017-07-20)
+            IPv6 ipv6Packet = (IPv6) ethPkt.getPayload();
+            if (ipv6Packet.getNextHeader() == IPv6.PROTOCOL_ICMP6) {
+                ICMP6 icmp6Packet = (ICMP6) ipv6Packet.getPayload();
+
+                if (icmp6Packet.getIcmpType() == ICMP6.ECHO_REQUEST) {
+                    log.info("IPV6 ICMP6 ECHO request to virtual gateway: srcIp={} dstIp={} nextHeader={}",
+                             srcIp, dstIp, ipv6Packet.getNextHeader());
+                    TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                                .setOutput(pkt.receivedFrom().port()).build();
+                    OutboundPacket packet =
+                        new DefaultOutboundPacket(pkt.receivedFrom().deviceId(), treatment,
+                                ByteBuffer.wrap(icmp6Packet.buildIcmp6Reply(pkt.parsed()).serialize()));
+                    packetService.emit(packet);
+                    return true;
+                }
+            }
+            log.warn("IPV6 packet to virtual gateway dropped: srcIp={} dstIp={} nextHeader={}",
+                     srcIp, dstIp, ipv6Packet.getNextHeader());
+            return true;
+
+        }
+        return false;  // unknown traffic
+    }
+
+    /**
+     * Routes packet reactively.
+     */
+    private void ipPacketReactiveProcessor(PacketContext context, Ethernet ethPkt, ConnectPoint srcCp,
+                                           IpAddress srcIp, IpAddress dstIp, byte ipProto) {
+        /* check reactive handling and forward packet */
+        log.trace("ip packet: srcCp={} srcIp={} dstIp={} ipProto={}",
+                  srcCp, srcIp, dstIp, ipProto);
+
+        EncapsulationType encap = EncapsulationType.NONE;
+
+        // prefix and nextHop for local Subnet
+        IpPrefix srcPrefix = srcIp.toIpPrefix();
+        IpPrefix dstPrefix = dstIp.toIpPrefix();
+        IpAddress srcNextHop = srcIp;
+        IpAddress dstNextHop = dstIp;
+        MacAddress treatmentSrcMac = ethPkt.getDestinationMAC();
+        int borderRoutePrefixLength = 0;
+        boolean isLocalSubnet = true;
+        boolean updateMac = simpleFabric.isVMac(ethPkt.getDestinationMAC());
+
+        // check subnet local or route
+        IpSubnet srcSubnet = simpleFabric.findIpSubnet(srcIp);
+        if (srcSubnet == null) {
+            Route route = simpleFabric.findBorderRoute(srcIp);
+            if (route == null) {
+                log.warn("route unknown: srcIp={}", srcIp);
+                return;
+            }
+            srcPrefix = route.prefix();
+            srcNextHop = route.nextHop();
+            borderRoutePrefixLength = route.prefix().prefixLength();
+            isLocalSubnet = false;
+        }
+        IpSubnet dstSubnet = simpleFabric.findIpSubnet(dstIp);
+        if (dstSubnet == null) {
+            Route route = simpleFabric.findBorderRoute(dstIp);
+            if (route == null) {
+                log.warn("route unknown: dstIp={}", dstIp);
+                return;
+            }
+            dstPrefix = route.prefix();
+            dstNextHop = route.nextHop();
+            borderRoutePrefixLength = route.prefix().prefixLength();
+            isLocalSubnet = false;
+        }
+
+        if (dstSubnet != null) {
+            // destination is local subnet ip
+            if (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR && dstSubnet.equals(srcSubnet)) {
+                // NOTE: if ALLOW_ETH_ADDRESS_SELECTOR=false; l2Forward is always false
+                L2Network l2Network = simpleFabric.findL2Network(dstSubnet.l2NetworkName());
+                treatmentSrcMac = ethPkt.getSourceMAC();
+                if (l2Network != null && l2Network.l2Forward()) {
+                    // NOTE: no reactive route action but do forward packet for L2Forward do not handle packet
+                    // update mac only if dstMac is virtualGatewayMac, else assume valid mac already for the l2 network
+                    log.info("LOCAL FORWARD ONLY: "
+                             + "srcCp={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
+                             context.inPacket().receivedFrom(),
+                             srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
+                             ethPkt.getVlanID(), ipProto, updateMac);
+                    forwardPacketToDstIp(context, dstIp, treatmentSrcMac, updateMac);
+                    return;
+                }
+            }
+            encap = dstSubnet.encapsulation();
+            if (encap == EncapsulationType.NONE && srcSubnet != null) {
+               encap = srcSubnet.encapsulation();
+            }
+        } else {
+            // destination is external network
+            if (srcSubnet == null) {
+                // both are externel network
+                log.warn("INVALID PACKET: srcIp and dstIp are both NON-LOCAL: "
+                         + "srcCP={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
+                         context.inPacket().receivedFrom(),
+                         srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
+                         ethPkt.getVlanID(), ipProto, updateMac);
+                return;
+            }
+            encap = srcSubnet.encapsulation();
+        }
+
+        log.info("REGI AND FORWARD: "
+                 + "srcCP={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
+                 context.inPacket().receivedFrom(),
+                 srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
+                 ethPkt.getVlanID(), ipProto, updateMac);
+        setUpConnectivity(srcCp, ipProto, srcPrefix, dstPrefix, dstNextHop, treatmentSrcMac, encap, updateMac,
+                          isLocalSubnet, borderRoutePrefixLength);
+        forwardPacketToDstIp(context, dstNextHop, treatmentSrcMac, updateMac);
+    }
+
+    /**
+     * Emits the specified packet onto the network.
+     */
+    private void forwardPacketToDstIp(PacketContext context, IpAddress nextHopIp,
+                                      MacAddress srcMac, boolean updateMac) {
+        Set<Host> hosts = hostService.getHostsByIp(nextHopIp);
+        Host dstHost;
+        if (!hosts.isEmpty()) {
+            dstHost = hosts.iterator().next();
+        } else {
+            // NOTE: hostService.requestMac(nextHopIp); NOT IMPLEMENTED in ONOS HostManager.java; do it myself
+            log.warn("forward packet nextHopIp host_mac unknown: nextHopIp={}", nextHopIp);
+            hostService.startMonitoringIp(nextHopIp);
+            simpleFabric.requestMac(nextHopIp);
+            // CONSIDER: make flood on all port of the dstHost's L2Network
+            return;
+        }
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(dstHost.location().port()).build();
+        OutboundPacket outPacket;
+        if (updateMac) {
+            // NOTE: eth address update by treatment is NOT applied, so update mac myself
+            outPacket = new DefaultOutboundPacket(dstHost.location().deviceId(), treatment,
+                                ByteBuffer.wrap(context.inPacket().parsed()
+                                          .setSourceMACAddress(srcMac)
+                                          .setDestinationMACAddress(dstHost.mac()).serialize()));
+        } else {
+            outPacket = new DefaultOutboundPacket(dstHost.location().deviceId(), treatment,
+                                context.inPacket().unparsed());
+        }
+        // be quiet on normal situation
+        log.info("forward packet: nextHopIP={} srcCP={} dstCP={}",
+                 nextHopIp, context.inPacket().receivedFrom(), dstHost.location());
+        packetService.emit(outPacket);
+    }
+
+    /**
+     * Update intents for connectivity.
+     *
+     * ToHost: dstPrefix = dstHostIp.toIpPrefix(), nextHopIp = destHostIp
+     * ToInternet: dstPrefix = route.prefix(), nextHopIp = route.nextHopIp
+     * returns intent submited or not
+     */
+    private boolean setUpConnectivity(ConnectPoint srcCp, byte ipProto, IpPrefix srcPrefix, IpPrefix dstPrefix,
+                                      IpAddress nextHopIp, MacAddress treatmentSrcMac,
+                                      EncapsulationType encap, boolean updateMac,
+                                      boolean isLocalSubnet, int borderRoutePrefixLength) {
+        Key key;
+        String keyProtoTag = "";
+        if (simpleFabric.REACTIVE_MATCH_IP_PROTO) {
+            keyProtoTag = "-p" + ipProto;
+        }
+        if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE) {
+            key = Key.of(srcPrefix.toString() + "-to-" + dstPrefix.toString() + keyProtoTag, reactiveAppId);
+        } else {
+            key = Key.of(dstPrefix.toString() + keyProtoTag, reactiveAppId);
+        }
+
+        if (!(simpleFabric.findL2Network(srcCp, VlanId.NONE) != null ||
+             (simpleFabric.REACTIVE_ALLOW_LINK_CP && !linkService.getIngressLinks(srcCp).isEmpty()))) {
+            log.warn("NO REGI for srcCp not in L2Network; srcCp={} srcPrefix={} dstPrefix={} nextHopIp={}",
+                      srcCp, srcPrefix, dstPrefix, nextHopIp);
+            return false;
+        }
+
+        MacAddress nextHopMac = null;
+        ConnectPoint egressPoint = null;
+        for (Host host : hostService.getHostsByIp(nextHopIp)) {
+            if (host.mac() != null) {
+                nextHopMac = host.mac();
+                egressPoint = host.location();
+                break;
+            }
+        }
+        if (nextHopMac == null || egressPoint == null) {
+            log.info("NO REGI for unknown nextHop Cp and Mac: srcPrefix={} dstPrefix={} nextHopIp={}",
+                     srcPrefix, dstPrefix, nextHopIp);
+            hostService.startMonitoringIp(nextHopIp);
+            simpleFabric.requestMac(nextHopIp);
+            return false;
+        }
+        TrafficTreatment treatment;
+        if (updateMac && simpleFabric.ALLOW_ETH_ADDRESS_SELECTOR) {
+            treatment = generateSetMacTreatment(nextHopMac, treatmentSrcMac);
+        } else {
+            treatment = DefaultTrafficTreatment.builder().build();
+        }
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        if (dstPrefix.isIp4()) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+            if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
+                selector.matchIPSrc(srcPrefix);
+            }
+            if (dstPrefix.prefixLength() > 0) {
+                selector.matchIPDst(dstPrefix);
+            }
+            if (ipProto != 0 && simpleFabric.REACTIVE_MATCH_IP_PROTO) {
+                selector.matchIPProtocol(ipProto);
+            }
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+            if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
+                selector.matchIPv6Src(srcPrefix);
+            }
+            if (dstPrefix.prefixLength() > 0) {
+                selector.matchIPv6Dst(dstPrefix);
+            }
+            if (ipProto != 0 && simpleFabric.REACTIVE_MATCH_IP_PROTO) {
+                selector.matchIPProtocol(ipProto);
+            }
+        }
+
+        // check and merge already existing ingress points
+        Set<ConnectPoint> ingressPoints = new HashSet<>();
+        MultiPointToSinglePointIntent existingIntent = (MultiPointToSinglePointIntent) intentService.getIntent(key);
+        if (existingIntent != null) {
+            ingressPoints.addAll(existingIntent.ingressPoints());
+            if (!ingressPoints.add(srcCp)  // alread exists and dst not changed
+                    && egressPoint.equals(existingIntent.egressPoint())
+                    && treatment.equals(existingIntent.treatment())) {
+                log.warn("srcCP is already in mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
+                         srcPrefix, dstPrefix, srcCp);
+                return false;
+            }
+            log.info("update mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
+                     srcPrefix, dstPrefix, srcCp);
+        } else {
+            log.info("create mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
+                     srcPrefix, dstPrefix, srcCp);
+            ingressPoints.add(srcCp);
+        }
+
+        // priority for forwarding case
+        int priority = reactivePriority(true, isLocalSubnet, borderRoutePrefixLength);
+
+        MultiPointToSinglePointIntent newIntent = MultiPointToSinglePointIntent.builder()
+            .key(key)
+            .appId(reactiveAppId)
+            .selector(selector.build())
+            .treatment(treatment)
+            .ingressPoints(ingressPoints)
+            .egressPoint(egressPoint)
+            .priority(priority)
+            .constraints(buildConstraints(reactiveConstraints, encap))
+            .build();
+        log.info("submmit mp2p intent: srcPrefix={} dstPrefix={} srcCp={} "
+                 + "newIntent={} nextHopIp={} nextHopMac={} priority={}",
+                 srcPrefix, dstPrefix, ingressPoints, newIntent, nextHopIp, nextHopMac, priority);
+        toBePurgedIntentKeys.remove(newIntent.key());
+        intentService.submit(newIntent);
+        return true;
+    }
+
+    // generate treatment to target
+    private TrafficTreatment generateSetMacTreatment(MacAddress dstMac, MacAddress srcMac) {
+        return DefaultTrafficTreatment.builder()
+                   // NOTE: Cisco Switch requires both src and dst mac set
+                   .setEthDst(dstMac)
+                   .setEthSrc(srcMac)
+                   .build();
+    }
+
+    // monitor border peers for routeService lookup to be effective
+    private void monitorBorderPeers() {
+        for (Route route : simpleFabric.getBorderRoutes()) {
+            hostService.startMonitoringIp(route.nextHop());
+            simpleFabric.requestMac(route.nextHop());
+        }
+    }
+
+    // priority calculator
+    private int reactivePriority(boolean isForward, boolean isLocalSubnet, int borderRoutePrefixLength) {
+        if (isLocalSubnet) {  // localSubnet <-> localSubnet
+            if (isForward) {
+                return simpleFabric.PRI_REACTIVE_LOCAL_FORWARD;
+            } else {  // isInterncept
+                return simpleFabric.PRI_REACTIVE_LOCAL_INTERCEPT;
+            }
+        } else {  // isBorder: localSubnet <-> boarderRouteGateway
+            int offset;
+            if (isForward) {
+                offset = simpleFabric.PRI_REACTIVE_BORDER_FORWARD;
+            } else {  // isIntercept
+                offset = simpleFabric.PRI_REACTIVE_BORDER_INTERCEPT;
+            }
+           return simpleFabric.PRI_REACTIVE_BORDER_BASE
+                  + borderRoutePrefixLength * simpleFabric.PRI_REACTIVE_BORDER_STEP + offset;
+        }
+    }
+
+    // constraints generator
+    private List<Constraint> buildConstraints(List<Constraint> constraints, EncapsulationType encap) {
+        if (!encap.equals(EncapsulationType.NONE)) {
+            List<Constraint> newConstraints = new ArrayList<>(constraints);
+            constraints.stream()
+                .filter(c -> c instanceof EncapsulationConstraint)
+                .forEach(newConstraints::remove);
+            newConstraints.add(new EncapsulationConstraint(encap));
+            return ImmutableList.copyOf(newConstraints);
+        }
+        return constraints;
+    }
+
+    // Dump Cli Handler
+    private void dump(String subject, PrintStream out) {
+        if (subject == "intents") {
+            out.println("Reactive Routing Route Intents:\n");
+            for (Intent entry : intentService.getIntents()) {
+                if (reactiveAppId.equals(entry.appId())) {
+                    MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
+                    out.println("    " + intent.key().toString()
+                                + " to " + intent.egressPoint().toString()
+                                + " set " + intent.treatment().immediate().toString()
+                                + " from " + intent.ingressPoints().toString());
+                }
+            }
+            out.println("");
+
+            out.println("Reactive Routing Intercept Flow Rules:\n");
+            List<FlowRule> rules = new ArrayList(interceptFlowRules);
+            Collections.sort(rules, new Comparator<FlowRule>() {
+                    @Override
+                    public int compare(FlowRule a, FlowRule b) {
+                        int r = a.deviceId().toString().compareTo(b.deviceId().toString());
+                        return (r != 0) ? r : Integer.compare(b.priority(), a.priority());  // descending on priority
+                    }
+                });
+            for (FlowRule rule : rules) {
+                out.println("    device=" + rule.deviceId().toString()
+                          + " priority=" + rule.priority()
+                          + " selector=" + rule.selector().criteria().toString());
+            }
+            out.println("");
+            out.println("Reactive Routing Intents to Be Purged:\n");
+            for (Key key: toBePurgedIntentKeys) {
+                out.println("    " + key.toString());
+            }
+            out.println("");
+
+        } else if (subject == "reactive-intents") {
+            for (Intent entry : intentService.getIntents()) {
+                if (reactiveAppId.equals(entry.appId())) {
+                    MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
+                    out.println(intent.key().toString()
+                                + " to " + intent.egressPoint().toString()
+                                + " set " + intent.treatment().immediate().toString()
+                                + " from " + intent.ingressPoints().toString());
+                }
+            }
+        }
+    }
+
+    // Listener
+    private class InternalSimpleFabricListener implements SimpleFabricListener {
+        @Override
+        public void event(SimpleFabricEvent event) {
+            switch (event.type()) {
+            case SIMPLE_FABRIC_UPDATED:
+                refreshIntercepts();
+                refreshRouteIntents();
+                checkIntentsPurge();
+                break;
+            case SIMPLE_FABRIC_FLUSH:
+                withdrawAllReactiveIntents();
+                checkIntentsPurge();
+                break;
+            case SIMPLE_FABRIC_IDLE:
+                refreshIntercepts();
+                refreshRouteIntents();
+                checkIntentsPurge();
+                monitorBorderPeers();
+                break;
+            case SIMPLE_FABRIC_DUMP:
+                dump(event.subject(), event.out());
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricService.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricService.java
new file mode 100644
index 0000000..c27a828
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.simplefabric;
+
+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.event.ListenerService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+
+import java.io.OutputStream;
+import java.util.Set;
+import java.util.Collection;
+
+/**
+ * Provides information about the routing configuration.
+ */
+public interface SimpleFabricService
+        extends ListenerService<SimpleFabricEvent, SimpleFabricListener> {
+
+    // App symbols
+    static final String APP_ID = "org.onosproject.simplefabric";
+    static final String L2FORWARD_APP_ID = "org.onosproject.simplefabric.l2forward";
+    static final String REACTIVE_APP_ID = "org.onosproject.simplefabric.reactive";
+
+    // Priority for l2NetworkRouting: L2NETWORK_UNICAST or L2NETWORK_BROADCAST
+    static final int PRI_L2NETWORK_UNICAST = 601;
+    static final int PRI_L2NETWORK_BROADCAST = 600;
+
+    // Reactive Routing within Local Subnets
+    // ASSUME: local subnets NEVER overlaps each other
+    static final int PRI_REACTIVE_LOCAL_FORWARD = 501;
+    static final int PRI_REACTIVE_LOCAL_INTERCEPT = 500;
+    // Reactive Routing for Border Routes with local subnet
+    // Priority: REACTIVE_BROUTE_BASE + routeIpPrefix * REACTIVE_BROUTE_STEP
+    //           + REACTIVE_BROUTE_FORWARD or REACTIVE_BROUTE_INTERCEPT
+    static final int PRI_REACTIVE_BORDER_BASE = 100;
+    static final int PRI_REACTIVE_BORDER_STEP = 2;
+    static final int PRI_REACTIVE_BORDER_FORWARD = 1;
+    static final int PRI_REACTIVE_BORDER_INTERCEPT = 0;
+
+    // Simple fabric event related timers
+    static final long IDLE_INTERVAL_MSEC = 5000;
+
+    // Feature control parameters
+    static final boolean ALLOW_IPV6 = false;
+    static final boolean ALLOW_ETH_ADDRESS_SELECTOR = true;
+    static final boolean REACTIVE_SINGLE_TO_SINGLE = false;
+    static final boolean REACTIVE_ALLOW_LINK_CP = false;  // MUST BE false (yjlee, 2017-10-18)
+    static final boolean REACTIVE_HASHED_PATH_SELECTION = false;
+    static final boolean REACTIVE_MATCH_IP_PROTO = false;
+
+    /**
+     * Gets appId.
+     *
+     * @return appId of simple fabric app
+     */
+    ApplicationId getAppId();
+
+    /**
+     * Gets all the l2Networks.
+     *
+     * @return all the l2Networks
+     */
+    Collection<L2Network> getL2Networks();
+
+    /**
+     * Retrieves the entire set of ipSubnets configuration.
+     *
+     * @return all the ipSubnets
+     */
+    Set<IpSubnet> getIpSubnets();
+
+    /**
+     * Retrieves the entire set of static routes to outer networks.
+     *
+     * @return the set of static routes to outer networks.
+     */
+    Set<Route> getBorderRoutes();
+
+    /**
+     * Gets Virtual Gateway Mac Address for Local Subnet Virtual Gateway Ip.
+     *
+     * @param ip the ip to check for Virtual Gateway Ip
+     * @return mac address of virtual gateway
+     */
+    MacAddress getVMacForIp(IpAddress ip);
+
+    /**
+     * Evaluates whether a mac is of Virtual Gateway Mac Addresses.
+     *
+     * @param mac the MacAddress to evaluate
+     * @return true if the mac is of any Vitrual Gateway Mac Address of ipSubnets
+     */
+    boolean isVMac(MacAddress mac);
+
+    /**
+     * Evaluates whether an Interface belongs to l2Networks.
+     *
+     * @param intf the interface to evaluate
+     * @return true if the inteface belongs to l2Networks configed, otherwise false
+     */
+    boolean isL2NetworkInterface(Interface intf);
+
+    /**
+     * Finds the L2 Network with given port and vlanId.
+     *
+     * @param port the port to be matched
+     * @param vlanId the vlanId to be matched
+     * @return the L2 Network for specific port and vlanId or null
+     */
+    L2Network findL2Network(ConnectPoint port, VlanId vlanId);
+
+    /**
+     * Finds the L2 Network of the name.
+     *
+     * @param name the name to be matched
+     * @return the L2 Network for specific name
+     */
+    L2Network findL2Network(String name);
+
+    /**
+     * Finds the IpSubnet containing the ipAddress.
+     *
+     * @param ipAddress the ipAddress to be matched
+     * @return the IpSubnet for specific ipAddress
+     */
+    IpSubnet findIpSubnet(IpAddress ipAddress);
+
+    /**
+     * Finds the Border Route containing the ipAddress.
+     * ASSUME: ipAddress is out of ipSubnets
+     *
+     * @param ipAddress the ipAddress to be matched
+     * @return the IpSubnet for specific ipAddress
+     */
+    Route findBorderRoute(IpAddress ipAddress);
+
+    /**
+     * Finds the network interface related to the host.
+     *
+     * @param host the host
+     * @return the interface related to the host
+     */
+    Interface getHostInterface(Host host);
+
+    /**
+     * Evaluates whether an IP address belongs to local SDN network.
+     *
+     * @param ipAddress the IP address to evaluate
+     * @return true if the IP address belongs to local SDN network, otherwise false
+     */
+    boolean isIpAddressLocal(IpAddress ipAddress);
+
+    /**
+     * Evaluates whether an IP prefix belongs to local SDN network.
+     *
+     * @param ipPrefix the IP prefix to evaluate
+     * @return true if the IP prefix belongs to local SDN network, otherwise false
+     */
+    boolean isIpPrefixLocal(IpPrefix ipPrefix);
+
+    /**
+     * Sends Neighbour Query (ARP or NDP) to Find Host Location.
+     *
+     * @param ip the ip address to resolve
+     * @return true if request mac packets are emitted. otherwise false
+     */
+    boolean requestMac(IpAddress ip);
+
+    /**
+     * Sends Dump Event to all SimpleFabricListeners to Dump Info on the Subject.
+     *
+     * @param subject the subject to dump
+     * @param out the output stream to dump
+     */
+    void dumpToStream(String subject, OutputStream out);
+
+    /**
+     * Triggers to send Refresh Notification to all sub modules.
+     */
+    void triggerRefresh();
+
+    /**
+     * Triggers to send Flush Notification to all sub modules.
+     */
+    void triggerFlush();
+
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/package-info.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/package-info.java
new file mode 100644
index 0000000..1e591a4
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Simple Leaf-Spine Network application.
+ */
+package org.onosproject.simplefabric;