ONOS-686, 687, 1344 : The first commit for the Segment Routing application
 - ICMP/ARP/IP handlers are implemented as a part of the application for now
 - Default routing and link add/failure/recovery are also supprted
 - Temporary NetworkConfigHandler, which is hardcoded to support only 6 router FISH topology, is used for test
 - Some fixes on GroupHanlder app to support transit routers
 - Supports multi-instance (tested with two instances)

Change-Id: Idfa67903e59e1c4cac4da430f89cd4c50e821420
diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandler.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandler.java
index ec9e42f..80f45c6 100644
--- a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandler.java
+++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandler.java
@@ -152,11 +152,13 @@
      * @param newLink new neighbor link
      */
     public void linkUp(Link newLink) {
+
         if (newLink.type() != Link.Type.DIRECT) {
             log.warn("linkUp: unknown link type");
             return;
         }
 
+
         if (!newLink.src().deviceId().equals(deviceId)) {
             log.warn("linkUp: deviceId{} doesn't match with link src{}",
                      deviceId,
@@ -307,13 +309,11 @@
 
         List<Integer> nsSegmentIds = new ArrayList<Integer>();
 
-        // Add one entry for "no label" (-1) to the list if
-        // dpid list has not more than one node/neighbor as
-        // there will never be a case a packet going to more than one
-        // neighbor without a label at an edge router
-        if (neighbors.size() == 1) {
-            nsSegmentIds.add(-1);
-        }
+        // Always pair up with no edge label
+        //If (neighbors.size() == 1) {
+        nsSegmentIds.add(-1);
+        //}
+
         // Filter out SegmentIds matching with the
         // nodes in the combo
         for (Integer sId : allSegmentIds) {
@@ -405,7 +405,8 @@
         }
     }
 
-    protected GroupKey getGroupKey(Object obj) {
+    public GroupKey getGroupKey(Object obj) {
         return new DefaultGroupKey(kryo.build().serialize(obj));
     }
+
 }
diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandlerApp.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandlerApp.java
index bb1a035..d8183d5 100644
--- a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandlerApp.java
+++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultGroupHandlerApp.java
@@ -90,6 +90,7 @@
     public void activate() {
         appId = coreService.registerApplication("org.onosproject.defaultgrouphandler");
         log.info("DefaultGroupHandlerApp Activating");
+
         deviceService.addListener(deviceListener);
         linkService.addListener(linkListener);
         for (Device device: deviceService.getDevices()) {
@@ -111,6 +112,7 @@
                           device.id());
             }
         }
+
         log.info("Activated");
     }
 
diff --git a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultTransitGroupHandler.java b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultTransitGroupHandler.java
index 1b5bb50..b820b09 100644
--- a/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultTransitGroupHandler.java
+++ b/apps/grouphandler/src/main/java/org/onosproject/grouphandler/DefaultTransitGroupHandler.java
@@ -138,7 +138,8 @@
                                             Set<DeviceId> updatedNeighbors) {
         Set<Set<DeviceId>> powerSet = getPowerSetOfNeighbors(updatedNeighbors);
 
-        Set<DeviceId> tmp = updatedNeighbors;
+        Set<DeviceId> tmp = new HashSet<DeviceId>();
+        tmp.addAll(updatedNeighbors);
         tmp.remove(impactedNeighbor);
         Set<Set<DeviceId>> tmpPowerSet = getPowerSetOfNeighbors(tmp);
 
diff --git a/apps/pom.xml b/apps/pom.xml
index 93f65b5..b008f1d 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -45,6 +45,8 @@
         <module>reactive-routing</module>
         <module>bgprouter</module>
         <module>test</module>
+        <module>grouphandler</module>
+        <module>segmentrouting</module>
     </modules>
 
     <properties>
diff --git a/apps/segmentrouting/pom.xml b/apps/segmentrouting/pom.xml
new file mode 100644
index 0000000..3e541f0
--- /dev/null
+++ b/apps/segmentrouting/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<!--

+  ~ Copyright 2014 Open Networking Laboratory

+  ~

+  ~ Licensed under the Apache License, Version 2.0 (the "License");

+  ~ you may not use this file except in compliance with the License.

+  ~ You may obtain a copy of the License at

+  ~

+  ~     http://www.apache.org/licenses/LICENSE-2.0

+  ~

+  ~ Unless required by applicable law or agreed to in writing, software

+  ~ distributed under the License is distributed on an "AS IS" BASIS,

+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  ~ See the License for the specific language governing permissions and

+  ~ limitations under the License.

+  --><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

+    <modelVersion>4.0.0</modelVersion>

+    <parent>

+    <artifactId>onos-apps</artifactId>

+    <groupId>org.onosproject</groupId>

+    <version>1.2.0-SNAPSHOT</version>

+  </parent>

+

+    <artifactId>onos-app-segmentrouting</artifactId>

+    <packaging>bundle</packaging>

+

+    <description>ONOS OSGi bundle archetype</description>

+    <url>http://onosproject.org</url>

+

+    <dependencies>

+        <dependency>

+            <groupId>org.onosproject</groupId>

+            <artifactId>onos-app-grouphandler</artifactId>

+            <version>1.2.0-SNAPSHOT</version>

+        </dependency>

+    </dependencies>

+

+</project>

diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
new file mode 100644
index 0000000..5b94518
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting;
+
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.HostId;
+import org.onosproject.net.packet.OutboundPacket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class ArpHandler {
+
+    private static Logger log = LoggerFactory.getLogger(ArpHandler.class);
+
+    private SegmentRoutingManager srManager;
+    private NetworkConfigHandler config;
+
+    /**
+     * Creates an ArpHandler object.
+     *
+     * @param srManager SegmentRoutingManager object
+     */
+    public ArpHandler(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+        this.config = checkNotNull(srManager.networkConfigHandler);
+    }
+
+    /**
+     * Processes incoming ARP packets.
+     * If it is an ARP request to router itself or known hosts,
+     * then it sends ARP response.
+     * If it is an ARP request to unknown hosts in its own subnet,
+     * then it flood the ARP request to the ports.
+     * If it is an ARP response, then set a flow rule for the host
+     * and forward any IP packets to the host in the packet buffer to the host.
+     *
+     * @param pkt incoming packet
+     */
+    public void processPacketIn(InboundPacket pkt) {
+
+        Ethernet ethernet = pkt.parsed();
+        ARP arp = (ARP) ethernet.getPayload();
+
+        ConnectPoint connectPoint = pkt.receivedFrom();
+        PortNumber inPort = connectPoint.port();
+        DeviceId deviceId = connectPoint.deviceId();
+        byte[] senderMacAddressByte = arp.getSenderHardwareAddress();
+        Ip4Address hostIpAddress = Ip4Address.valueOf(arp.getSenderProtocolAddress());
+
+        srManager.routingRulePopulator.populateIpRuleForHost(deviceId, hostIpAddress, MacAddress.
+                valueOf(senderMacAddressByte), inPort);
+
+        if (arp.getOpCode() == ARP.OP_REQUEST) {
+            handleArpRequest(deviceId, connectPoint, ethernet);
+        } else {
+            srManager.ipHandler.forwardPackets(deviceId, hostIpAddress);
+        }
+    }
+
+    private void handleArpRequest(DeviceId deviceId, ConnectPoint inPort, Ethernet payload) {
+
+        ARP arpRequest = (ARP) payload.getPayload();
+        HostId targetHostId = HostId.hostId(MacAddress.valueOf(
+                arpRequest.getTargetHardwareAddress()));
+
+        // ARP request for router
+        if (isArpReqForRouter(deviceId, arpRequest)) {
+            Ip4Address targetAddress = Ip4Address.valueOf(arpRequest.getTargetProtocolAddress());
+            sendArpResponse(arpRequest, config.getRouterMac(targetAddress));
+
+        // ARP request for known hosts
+        } else if (srManager.hostService.getHost(targetHostId) != null) {
+            MacAddress targetMac = srManager.hostService.getHost(targetHostId).mac();
+            sendArpResponse(arpRequest, targetMac);
+
+        // ARP request for unknown host in the subnet
+        } else if (isArpReqForSubnet(deviceId, arpRequest)) {
+            flood(payload, inPort);
+        }
+    }
+
+
+    private boolean isArpReqForRouter(DeviceId deviceId, ARP arpRequest) {
+        Ip4Address gatewayIpAddress = config.getGatewayIpAddress(deviceId);
+        if (gatewayIpAddress != null) {
+            Ip4Address targetProtocolAddress = Ip4Address.valueOf(arpRequest
+                    .getTargetProtocolAddress());
+            if (gatewayIpAddress.equals(targetProtocolAddress)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isArpReqForSubnet(DeviceId deviceId, ARP arpRequest) {
+        String subnetInfo = config.getSubnetInfo(deviceId);
+        if (subnetInfo != null) {
+            IpPrefix prefix = IpPrefix.valueOf(subnetInfo);
+            if (prefix.contains(Ip4Address.valueOf(arpRequest.getTargetProtocolAddress()))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Sends an APR request for the target IP address to all ports except in-port.
+     *
+     * @param deviceId Switch device ID
+     * @param targetAddress target IP address for ARP
+     * @param inPort in-port
+     */
+    public void sendArpRequest(DeviceId deviceId, IpAddress targetAddress, ConnectPoint inPort) {
+
+        byte[] senderMacAddress = config.getRouterMacAddress(deviceId).toBytes();
+        byte[] senderIpAddress = config.getRouterIpAddress(deviceId)
+                .getIp4Prefix().address().toOctets();
+
+        ARP arpRequest = new ARP();
+        arpRequest.setHardwareType(ARP.HW_TYPE_ETHERNET)
+                  .setProtocolType(ARP.PROTO_TYPE_IP)
+                  .setHardwareAddressLength(
+                        (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+                  .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH)
+                  .setOpCode(ARP.OP_REQUEST)
+                  .setSenderHardwareAddress(senderMacAddress)
+                  .setTargetHardwareAddress(MacAddress.ZERO.toBytes())
+                  .setSenderProtocolAddress(senderIpAddress)
+                  .setTargetProtocolAddress(targetAddress.toOctets());
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(MacAddress.BROADCAST.toBytes())
+                .setSourceMACAddress(senderMacAddress)
+                .setEtherType(Ethernet.TYPE_ARP).setPayload(arpRequest);
+
+        flood(eth, inPort);
+    }
+
+    private void sendArpResponse(ARP arpRequest, MacAddress targetMac) {
+
+        ARP arpReply = new ARP();
+        arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
+                .setProtocolType(ARP.PROTO_TYPE_IP)
+                .setHardwareAddressLength(
+                        (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+                .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH)
+                .setOpCode(ARP.OP_REPLY)
+                .setSenderHardwareAddress(targetMac.toBytes())
+                .setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
+                .setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
+                .setTargetProtocolAddress(arpRequest.getSenderProtocolAddress());
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
+                .setSourceMACAddress(targetMac.toBytes())
+                .setEtherType(Ethernet.TYPE_ARP).setPayload(arpReply);
+
+
+        HostId dstId = HostId.hostId(MacAddress.valueOf(
+                arpReply.getTargetHardwareAddress()));
+        Host dst = srManager.hostService.getHost(dstId);
+        if (dst == null) {
+            log.warn("Cannot send ARP response to unknown device");
+            return;
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().
+                setOutput(dst.location().port()).build();
+        OutboundPacket packet = new DefaultOutboundPacket(dst.location().deviceId(),
+                treatment, ByteBuffer.wrap(eth.serialize()));
+
+        srManager.packetService.emit(packet);
+    }
+
+    private void flood(Ethernet request, ConnectPoint inPort) {
+        TrafficTreatment.Builder builder;
+        ByteBuffer buf = ByteBuffer.wrap(request.serialize());
+
+        for (Port port: srManager.deviceService.getPorts(inPort.deviceId())) {
+            if (!port.number().equals(inPort.port()) &&
+                    port.number().toLong() > 0) {
+                builder = DefaultTrafficTreatment.builder();
+                builder.setOutput(port.number());
+                srManager.packetService.emit(new DefaultOutboundPacket(inPort.deviceId(),
+                        builder.build(), buf));
+            }
+        }
+    }
+
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
new file mode 100644
index 0000000..3684ec9
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting;
+
+import org.onlab.packet.IpPrefix;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.flow.FlowRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class DefaultRoutingHandler {
+
+    private static Logger log = LoggerFactory.getLogger(DefaultRoutingHandler.class);
+
+    private SegmentRoutingManager srManager;
+    private RoutingRulePopulator rulePopulator;
+    private NetworkConfigHandler config;
+    private Status populationStatus;
+
+    /**
+     * Represents the default routing population status.
+     */
+    public enum Status {
+        // population process is not started yet.
+        IDLE,
+
+        // population process started.
+        STARTED,
+
+        // population process was aborted due to errors, mostly for groups not found.
+        ABORTED,
+
+        // population process was finished successfully.
+        SUCCEEDED
+    }
+
+    /**
+     * Creates a DefaultRoutingHandler object.
+     *
+     * @param srManager SegmentRoutingManager object
+     */
+    public DefaultRoutingHandler(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+        this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
+        this.config = checkNotNull(srManager.networkConfigHandler);
+        this.populationStatus = Status.IDLE;
+    }
+
+    /**
+     * Populates all routing rules to all connected routers, including default
+     * routing rules, adjacency rules, and policy rules if any.
+     *
+     * @return true if it succeeds in populating all rules, otherwise false
+     */
+    public boolean populateAllRoutingRules() {
+
+        populationStatus = Status.STARTED;
+        log.info("Starts to populate routing rules");
+
+        for (Device sw : srManager.deviceService.getDevices()) {
+            if (srManager.mastershipService.
+                    getLocalRole(sw.id()) != MastershipRole.MASTER) {
+                continue;
+            }
+
+            ECMPShortestPathGraph ecmpSPG = new ECMPShortestPathGraph(sw.id(), srManager);
+            if (!populateEcmpRoutingRules(sw, ecmpSPG)) {
+                populationStatus = Status.ABORTED;
+                log.debug("Abort routing rule population");
+                return false;
+            }
+
+            // TODO: Set adjacency routing rule for all switches
+        }
+
+        populationStatus = Status.SUCCEEDED;
+        log.info("Completes routing rule population");
+        return true;
+    }
+
+    private boolean populateEcmpRoutingRules(Device sw,
+                                             ECMPShortestPathGraph ecmpSPG) {
+
+        HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia =
+                ecmpSPG.getAllLearnedSwitchesAndVia();
+        for (Integer itrIdx : switchVia.keySet()) {
+            HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap =
+                    switchVia.get(itrIdx);
+            for (DeviceId targetSw : swViaMap.keySet()) {
+                DeviceId destSw = sw.id();
+                Set<DeviceId> nextHops = new HashSet<>();
+
+                for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) {
+                    if (via.isEmpty()) {
+                        nextHops.add(destSw);
+                    } else {
+                        nextHops.add(via.get(0));
+                    }
+                }
+                if (!populateEcmpRoutingRulePartial(targetSw, destSw, nextHops)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private boolean populateEcmpRoutingRulePartial(DeviceId targetSw, DeviceId destSw,
+                                Set<DeviceId> nextHops) {
+        boolean result;
+
+        if (nextHops.isEmpty()) {
+            nextHops.add(destSw);
+        }
+
+        // If both target switch and dest switch are edge routers, then set IP rule
+        // for both subnet and router IP.
+        if (config.isEdgeRouter(targetSw) && config.isEdgeRouter(destSw)) {
+            String subnets = config.getSubnetInfo(destSw);
+            result = rulePopulator.populateIpRuleForSubnet(targetSw, subnets, destSw, nextHops);
+            if (!result) {
+                return false;
+            }
+
+            IpPrefix routerIp = config.getRouterIpAddress(destSw);
+            result = rulePopulator.populateIpRuleForRouter(targetSw, routerIp, destSw, nextHops);
+            if (!result) {
+                return false;
+            }
+
+        // If the target switch is an edge router, then set IP rules for the router IP.
+        } else if (config.isEdgeRouter(targetSw)) {
+            IpPrefix routerIp = config.getRouterIpAddress(destSw);
+            result = rulePopulator.populateIpRuleForRouter(targetSw, routerIp, destSw, nextHops);
+            if (!result) {
+                return false;
+            }
+
+        // If the target switch is an transit router, then set MPLS rules only.
+        } else if (config.isTransitRouter(targetSw)) {
+            result = rulePopulator.populateMplsRule(targetSw, destSw, nextHops);
+            if (!result) {
+                return false;
+            }
+        } else {
+            log.warn("The switch {} is neither an edge router nor a transit router.", targetSw);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Populates table miss entries for all tables, and pipeline rules for
+     * VLAN and TACM tables.
+     *
+     * @param deviceId Switch ID to set the rules
+     */
+    public void populateTtpRules(DeviceId deviceId) {
+
+        rulePopulator.populateTableMissEntry(deviceId, FlowRule.Type.VLAN,
+                true, false, false, FlowRule.Type.DEFAULT);
+        rulePopulator.populateTableMissEntry(deviceId, FlowRule.Type.ETHER,
+                true, false, false, FlowRule.Type.DEFAULT);
+        rulePopulator.populateTableMissEntry(deviceId, FlowRule.Type.IP,
+                false, true, true, FlowRule.Type.ACL);
+        rulePopulator.populateTableMissEntry(deviceId, FlowRule.Type.MPLS,
+                false, true, true, FlowRule.Type.ACL);
+        rulePopulator.populateTableMissEntry(deviceId, FlowRule.Type.ACL,
+                false, false, false, FlowRule.Type.DEFAULT);
+
+        rulePopulator.populateTableVlan(deviceId);
+        rulePopulator.populateTableTMac(deviceId);
+    }
+
+    /**
+     * Start the flow rule population process if it was never started.
+     * The process finishes successfully when all flow rules are set and
+     * stops with ABORTED status when any groups required for flows is not
+     * set yet.
+     */
+    public void startPopulationProcess() {
+        synchronized (populationStatus) {
+            if (populationStatus == Status.IDLE ||
+                    populationStatus == Status.SUCCEEDED) {
+                populationStatus = Status.STARTED;
+                populateAllRoutingRules();
+            }
+        }
+    }
+
+    /**
+     * Resume the flow rule population process if it was aborted for any reason.
+     * Mostly the process is aborted when the groups required are not set yet.
+     */
+    public void resumePopulationProcess() {
+        synchronized (populationStatus) {
+            if (populationStatus == Status.ABORTED) {
+                populationStatus = Status.STARTED;
+                // TODO: we need to restart from the point aborted instead of restarting.
+                populateAllRoutingRules();
+            }
+        }
+    }
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java
new file mode 100644
index 0000000..17cfd96
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java
@@ -0,0 +1,190 @@
+package org.onosproject.segmentrouting;
+
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onosproject.grouphandler.DeviceProperties;
+import org.onosproject.net.DeviceId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DeviceConfiguration implements DeviceProperties {
+
+    private static final Logger log = LoggerFactory
+            .getLogger(DeviceConfiguration.class);
+    private final List<Integer> allSegmentIds =
+            Arrays.asList(101, 102, 103, 104, 105, 106);
+    private HashMap<DeviceId, Integer> deviceSegmentIdMap =
+            new HashMap<DeviceId, Integer>() {
+                {
+                    put(DeviceId.deviceId("of:0000000000000001"), 101);
+                    put(DeviceId.deviceId("of:0000000000000002"), 102);
+                    put(DeviceId.deviceId("of:0000000000000003"), 103);
+                    put(DeviceId.deviceId("of:0000000000000004"), 104);
+                    put(DeviceId.deviceId("of:0000000000000005"), 105);
+                    put(DeviceId.deviceId("of:0000000000000006"), 106);
+                }
+            };
+    private final HashMap<DeviceId, MacAddress> deviceMacMap =
+            new HashMap<DeviceId, MacAddress>() {
+                {
+                    put(DeviceId.deviceId("of:0000000000000001"),
+                            MacAddress.valueOf("00:00:00:00:00:01"));
+                    put(DeviceId.deviceId("of:0000000000000002"),
+                            MacAddress.valueOf("00:00:00:00:00:02"));
+                    put(DeviceId.deviceId("of:0000000000000003"),
+                            MacAddress.valueOf("00:00:00:00:00:03"));
+                    put(DeviceId.deviceId("of:0000000000000004"),
+                            MacAddress.valueOf("00:00:00:00:00:04"));
+                    put(DeviceId.deviceId("of:0000000000000005"),
+                            MacAddress.valueOf("00:00:00:00:00:05"));
+                    put(DeviceId.deviceId("of:0000000000000006"),
+                            MacAddress.valueOf("00:00:00:00:00:06"));
+                }
+            };
+
+    private final HashMap<DeviceId, Ip4Address> deviceIpMap =
+            new HashMap<DeviceId, Ip4Address>() {
+                {
+                    put(DeviceId.deviceId("of:0000000000000001"),
+                            Ip4Address.valueOf("192.168.0.1"));
+                    put(DeviceId.deviceId("of:0000000000000002"),
+                            Ip4Address.valueOf("192.168.0.2"));
+                    put(DeviceId.deviceId("of:0000000000000003"),
+                            Ip4Address.valueOf("192.168.0.3"));
+                    put(DeviceId.deviceId("of:0000000000000004"),
+                            Ip4Address.valueOf("192.168.0.4"));
+                    put(DeviceId.deviceId("of:0000000000000005"),
+                            Ip4Address.valueOf("192.168.0.5"));
+                    put(DeviceId.deviceId("of:0000000000000006"),
+                            Ip4Address.valueOf("192.168.0.6"));
+                }
+            };
+
+    @Override
+    public int getSegmentId(DeviceId deviceId) {
+        if (deviceSegmentIdMap.get(deviceId) != null) {
+            log.debug("getSegmentId for device{} is {}",
+                    deviceId,
+                    deviceSegmentIdMap.get(deviceId));
+            return deviceSegmentIdMap.get(deviceId);
+        } else {
+            throw new IllegalStateException();
+        }
+    }
+
+
+    @Override
+    public MacAddress getDeviceMac(DeviceId deviceId) {
+        if (deviceMacMap.get(deviceId) != null) {
+            log.debug("getDeviceMac for device{} is {}",
+                    deviceId,
+                    deviceMacMap.get(deviceId));
+            return deviceMacMap.get(deviceId);
+        } else {
+            throw new IllegalStateException();
+        }
+    }
+
+
+    @Override
+    public boolean isEdgeDevice(DeviceId deviceId) {
+        if (deviceId.equals(DeviceId.deviceId("of:0000000000000001"))
+                || deviceId.equals(DeviceId.deviceId("of:0000000000000006"))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public List<Integer> getAllDeviceSegmentIds() {
+        return allSegmentIds;
+    }
+
+
+    /**
+     * Returns Segment ID for the router with the MAC address given.
+     *
+     * @param targetMac Mac address for the router
+     * @return Segment ID for the router with the MAC address
+     */
+    public int getSegmentId(MacAddress targetMac) {
+        for (Map.Entry<DeviceId, MacAddress> entry: deviceMacMap.entrySet()) {
+            if (entry.getValue().equals(targetMac)) {
+                return deviceSegmentIdMap.get(entry.getKey());
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Returns Segment ID for the router withe IP address given.
+     *
+     * @param targetAddress IP address of the router
+     * @return Segment ID for the router with the IP address
+     */
+    public int getSegmentId(Ip4Address targetAddress) {
+        for (Map.Entry<DeviceId, Ip4Address> entry: deviceIpMap.entrySet()) {
+            if (entry.getValue().equals(targetAddress)) {
+                return deviceSegmentIdMap.get(entry.getKey());
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Returns Router IP address for the router with the device ID given.
+     *
+     * @param deviceId device ID of the router
+     * @return IP address of the router
+     */
+    public Ip4Address getRouterIp(DeviceId deviceId) {
+        if (deviceIpMap.get(deviceId) != null) {
+            log.debug("getDeviceIp for device{} is {}",
+                    deviceId,
+                    deviceIpMap.get(deviceId));
+            return deviceIpMap.get(deviceId);
+        } else {
+            throw new IllegalStateException();
+        }
+    }
+
+    /**
+     * Returns the Device ID of the router with the Segment ID given.
+     *
+     * @param sid Segment ID of the router
+     * @return Device ID of the router
+     */
+    public DeviceId getDeviceId(int sid) {
+        for (Map.Entry<DeviceId, Integer> entry: deviceSegmentIdMap.entrySet()) {
+            if (entry.getValue() == sid) {
+                return entry.getKey();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the Device ID of the router with the IP address given.
+     *
+     * @param ipAddress IP address of the router
+     * @return Device ID of the router
+     */
+    public DeviceId getDeviceId(Ip4Address ipAddress) {
+        for (Map.Entry<DeviceId, Ip4Address> entry: deviceIpMap.entrySet()) {
+            if (entry.getValue().equals(ipAddress)) {
+                return entry.getKey();
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ECMPShortestPathGraph.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ECMPShortestPathGraph.java
new file mode 100644
index 0000000..cd28fcf
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ECMPShortestPathGraph.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting;
+
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPath;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This class creates bandwidth constrained breadth first tree and returns paths
+ * from root Device to leaf Devicees which satisfies the bandwidth condition. If
+ * bandwidth parameter is not specified, the normal breadth first tree will be
+ * calculated. The paths are snapshot paths at the point of the class
+ * instantiation.
+ */
+public class ECMPShortestPathGraph {
+    LinkedList<DeviceId> deviceQueue = new LinkedList<>();
+    LinkedList<Integer> distanceQueue = new LinkedList<>();
+    HashMap<DeviceId, Integer> deviceSearched = new HashMap<>();
+    HashMap<DeviceId, ArrayList<Link>> upstreamLinks = new HashMap<>();
+    HashMap<DeviceId, ArrayList<Path>> paths = new HashMap<>();
+    HashMap<Integer, ArrayList<DeviceId>> distanceDeviceMap = new HashMap<>();
+    DeviceId rootDevice;
+    private SegmentRoutingManager srManager;
+    private static final Logger log = LoggerFactory
+            .getLogger(ECMPShortestPathGraph.class);
+
+    /**
+     * Constructor.
+     *
+     * @param rootDevice root of the BFS tree
+     * @param linkListToAvoid link list to avoid
+     * @param deviceIdListToAvoid device list to avoid
+     */
+    public ECMPShortestPathGraph(DeviceId rootDevice, List<String> deviceIdListToAvoid,
+                                 List<Link> linkListToAvoid) {
+        this.rootDevice = rootDevice;
+        calcECMPShortestPathGraph(deviceIdListToAvoid, linkListToAvoid);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param rootDevice root of the BFS tree
+     * @param srManager SegmentRoutingManager object
+     */
+    public ECMPShortestPathGraph(DeviceId rootDevice, SegmentRoutingManager srManager) {
+        this.rootDevice = rootDevice;
+        this.srManager = srManager;
+        calcECMPShortestPathGraph();
+    }
+
+    /**
+     * Calculates the BFS tree using any provided constraints and Intents.
+     */
+    private void calcECMPShortestPathGraph() {
+        deviceQueue.add(rootDevice);
+        int currDistance = 0;
+        distanceQueue.add(currDistance);
+        deviceSearched.put(rootDevice, currDistance);
+        while (!deviceQueue.isEmpty()) {
+            DeviceId sw = deviceQueue.poll();
+            DeviceId prevSw = null;
+            currDistance = distanceQueue.poll();
+
+            for (Link link : srManager.linkService.getDeviceEgressLinks(sw)) {
+                DeviceId reachedDevice = link.dst().deviceId();
+                if ((prevSw != null)
+                        && (prevSw.equals(reachedDevice))) {
+                    /* Ignore LAG links between the same set of Devicees */
+                    continue;
+                } else  {
+                    prevSw = reachedDevice;
+                }
+
+                Integer distance = deviceSearched.get(reachedDevice);
+                if ((distance != null) && (distance.intValue() < (currDistance + 1))) {
+                    continue;
+                }
+                if (distance == null) {
+                    /* First time visiting this Device node */
+                    deviceQueue.add(reachedDevice);
+                    distanceQueue.add(currDistance + 1);
+                    deviceSearched.put(reachedDevice, currDistance + 1);
+
+                    ArrayList<DeviceId> distanceSwArray = distanceDeviceMap
+                            .get(currDistance + 1);
+                    if (distanceSwArray == null) {
+                        distanceSwArray = new ArrayList<DeviceId>();
+                        distanceSwArray.add(reachedDevice);
+                        distanceDeviceMap.put(currDistance + 1, distanceSwArray);
+                    } else {
+                        distanceSwArray.add(reachedDevice);
+                    }
+                }
+
+                ArrayList<Link> upstreamLinkArray =
+                        upstreamLinks.get(reachedDevice);
+                if (upstreamLinkArray == null) {
+                    upstreamLinkArray = new ArrayList<Link>();
+                    upstreamLinkArray.add(copyDefaultLink(link));
+                    //upstreamLinkArray.add(link);
+                    upstreamLinks.put(reachedDevice, upstreamLinkArray);
+                } else {
+                    /* ECMP links */
+                    upstreamLinkArray.add(copyDefaultLink(link));
+                }
+            }
+        }
+    }
+
+    /**
+     * Calculates the BFS tree using any provided constraints and Intents.
+     */
+    private void calcECMPShortestPathGraph(List<String> deviceIdListToAvoid, List<Link> linksToAvoid) {
+        deviceQueue.add(rootDevice);
+        int currDistance = 0;
+        distanceQueue.add(currDistance);
+        deviceSearched.put(rootDevice, currDistance);
+        boolean foundLinkToAvoid = false;
+        while (!deviceQueue.isEmpty()) {
+            DeviceId sw = deviceQueue.poll();
+            DeviceId prevSw = null;
+            currDistance = distanceQueue.poll();
+            for (Link link : srManager.linkService.getDeviceEgressLinks(sw)) {
+                for (Link linkToAvoid: linksToAvoid) {
+                    // TODO: equls should work
+                    //if (link.equals(linkToAvoid)) {
+                    if (linkContains(link, linksToAvoid)) {
+                        foundLinkToAvoid = true;
+                        break;
+                    }
+                }
+                if (foundLinkToAvoid) {
+                    foundLinkToAvoid = false;
+                    continue;
+                }
+                DeviceId reachedDevice = link.dst().deviceId();
+                if (deviceIdListToAvoid.contains(reachedDevice.toString())) {
+                    continue;
+                }
+                if ((prevSw != null)
+                        && (prevSw.equals(reachedDevice))) {
+                    /* Ignore LAG links between the same set of Devicees */
+                    continue;
+                } else {
+                    prevSw = reachedDevice;
+                }
+
+                Integer distance = deviceSearched.get(reachedDevice);
+                if ((distance != null) && (distance.intValue() < (currDistance + 1))) {
+                    continue;
+                }
+                if (distance == null) {
+                    /* First time visiting this Device node */
+                    deviceQueue.add(reachedDevice);
+                    distanceQueue.add(currDistance + 1);
+                    deviceSearched.put(reachedDevice, currDistance + 1);
+
+                    ArrayList<DeviceId> distanceSwArray = distanceDeviceMap
+                            .get(currDistance + 1);
+                    if (distanceSwArray == null) {
+                        distanceSwArray = new ArrayList<DeviceId>();
+                        distanceSwArray.add(reachedDevice);
+                        distanceDeviceMap.put(currDistance + 1, distanceSwArray);
+                    } else {
+                        distanceSwArray.add(reachedDevice);
+                    }
+                }
+
+                ArrayList<Link> upstreamLinkArray =
+                        upstreamLinks.get(reachedDevice);
+                if (upstreamLinkArray == null) {
+                    upstreamLinkArray = new ArrayList<Link>();
+                    upstreamLinkArray.add(copyDefaultLink(link));
+                    upstreamLinks.put(reachedDevice, upstreamLinkArray);
+                } else {
+                    /* ECMP links */
+                    upstreamLinkArray.add(copyDefaultLink(link));
+                }
+            }
+        }
+    }
+
+
+    private boolean linkContains(Link link, List<Link> links) {
+
+        DeviceId srcDevice1 = link.src().deviceId();
+        DeviceId dstDevice1 = link.dst().deviceId();
+        long srcPort1 = link.src().port().toLong();
+        long dstPort1 = link.dst().port().toLong();
+
+        for (Link link2: links) {
+            DeviceId srcDevice2 = link2.src().deviceId();
+            DeviceId dstDevice2 = link2.dst().deviceId();
+            long srcPort2 = link2.src().port().toLong();
+            long dstPort2 = link2.dst().port().toLong();
+
+            if (srcDevice1.toString().equals(srcDevice2.toString())
+                    && dstDevice1.toString().equals(dstDevice2.toString())
+                    && srcPort1 == srcPort2 && dstPort1 == dstPort2) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void getDFSPaths(DeviceId dstDeviceDeviceId, Path path, ArrayList<Path> paths) {
+        DeviceId rootDeviceDeviceId = rootDevice;
+        for (Link upstreamLink : upstreamLinks.get(dstDeviceDeviceId)) {
+            /* Deep clone the path object */
+            Path sofarPath;
+            ArrayList<Link> sofarLinks = new ArrayList<Link>();
+            if (path != null && !path.links().isEmpty()) {
+                sofarLinks.addAll(path.links());
+            }
+            sofarLinks.add(upstreamLink);
+            sofarPath = new DefaultPath(ProviderId.NONE, sofarLinks, 0);
+            if (upstreamLink.src().deviceId().equals(rootDeviceDeviceId)) {
+                paths.add(sofarPath);
+                return;
+            } else {
+                getDFSPaths(upstreamLink.src().deviceId(), sofarPath, paths);
+            }
+        }
+    }
+
+    /**
+     * Return root Device for the graph.
+     *
+     * @return root Device
+     */
+    public DeviceId getRootDevice() {
+        return rootDevice;
+    }
+
+    /**
+     * Return the computed ECMP paths from the root Device to a given Device in
+     * the network.
+     *
+     * @param targetDevice the target Device
+     * @return the list of ECMP Paths from the root Device to the target Device
+     */
+    public ArrayList<Path> getECMPPaths(DeviceId targetDevice) {
+        ArrayList<Path> pathArray = paths.get(targetDevice);
+        if (pathArray == null && deviceSearched.containsKey(
+                targetDevice)) {
+            pathArray = new ArrayList<>();
+            DeviceId sw = targetDevice;
+            getDFSPaths(sw, null, pathArray);
+            paths.put(targetDevice, pathArray);
+        }
+        return pathArray;
+    }
+
+    /**
+     * Return the complete info of the computed ECMP paths for each Device
+     * learned in multiple iterations from the root Device.
+     *
+     * @return the hash table of Devicees learned in multiple Dijkstra
+     *         iterations and corresponding ECMP paths to it from the root
+     *         Device
+     */
+    public HashMap<Integer, HashMap<DeviceId,
+            ArrayList<Path>>> getCompleteLearnedDeviceesAndPaths() {
+
+        HashMap<Integer, HashMap<DeviceId, ArrayList<Path>>> pathGraph = new
+                HashMap<Integer, HashMap<DeviceId, ArrayList<Path>>>();
+
+        for (Integer itrIndx : distanceDeviceMap.keySet()) {
+            HashMap<DeviceId, ArrayList<Path>> swMap = new
+                    HashMap<DeviceId, ArrayList<Path>>();
+            for (DeviceId sw : distanceDeviceMap.get(itrIndx)) {
+                swMap.put(sw, getECMPPaths(sw));
+            }
+            pathGraph.put(itrIndx, swMap);
+        }
+
+        return pathGraph;
+    }
+
+    /**
+     * Return the complete info of the computed ECMP paths for each Device
+     * learned in multiple iterations from the root Device.
+     *
+     * @return the hash table of Devicees learned in multiple Dijkstra
+     *         iterations and corresponding ECMP paths in terms of Devicees to
+     *         be traversed to it from the root Device
+     */
+    public HashMap<Integer, HashMap<DeviceId,
+            ArrayList<ArrayList<DeviceId>>>> getAllLearnedSwitchesAndVia() {
+
+        HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> deviceViaMap =
+                new HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>();
+
+        for (Integer itrIndx : distanceDeviceMap.keySet()) {
+            HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swMap =
+                    new HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>();
+
+            for (DeviceId sw : distanceDeviceMap.get(itrIndx)) {
+                ArrayList<ArrayList<DeviceId>> swViaArray = new ArrayList<>();
+                for (Path path : getECMPPaths(sw)) {
+                    ArrayList<DeviceId> swVia = new ArrayList<>();
+                    for (Link link : path.links()) {
+                        if (link.src().deviceId().equals(rootDevice)) {
+                            /* No need to add the root Device again in
+                             * the Via list
+                             */
+                            continue;
+                        }
+                        swVia.add(link.src().deviceId());
+                    }
+                    swViaArray.add(swVia);
+                }
+                swMap.put(sw, swViaArray);
+            }
+            deviceViaMap.put(itrIndx, swMap);
+        }
+        return deviceViaMap;
+    }
+
+
+    private Link copyDefaultLink(Link link) {
+        DefaultLink src = (DefaultLink) link;
+        DefaultLink defaultLink = new DefaultLink(src.providerId(), src.src(),
+                src.dst(), src.type(), src.annotations());
+
+        return defaultLink;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sBuilder = new StringBuilder();
+        for (Device device: srManager.deviceService.getDevices()) {
+            if (device.id() != rootDevice) {
+                sBuilder.append("Paths from" + rootDevice + " to " + device.id() + "\r\n");
+                ArrayList<Path> paths = getECMPPaths(device.id());
+                if (paths != null) {
+                    for (Path path : paths) {
+                        for (Link link : path.links()) {
+                            sBuilder.append(" : " + link.src() + " -> " + link.dst());
+                        }
+                    }
+                }
+            }
+        }
+        return sBuilder.toString();
+    }
+}
+
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
new file mode 100644
index 0000000..3bfb888
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting;
+
+import java.nio.ByteBuffer;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MPLS;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class IcmpHandler {
+
+    private static Logger log = LoggerFactory.getLogger(IcmpHandler.class);
+    private SegmentRoutingManager srManager;
+    private NetworkConfigHandler config;
+
+    /**
+     * Creates an IcmpHandler object.
+     *
+     * @param srManager SegmentRoutingManager object
+     */
+    public IcmpHandler(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+        this.config = checkNotNull(srManager.networkConfigHandler);
+    }
+
+    /**
+     * Process incoming ICMP packet.
+     * If it is an ICMP request to router or known host, then sends an ICMP response.
+     * If it is an ICMP packet to known host and forward the packet to the host.
+     * If it is an ICMP packet to unknown host in a subnet, then sends an ARP request
+     * to the subnet.
+     *
+     * @param pkt
+     */
+    public void processPacketIn(InboundPacket pkt) {
+
+        Ethernet ethernet = pkt.parsed();
+        IPv4 ipv4 = (IPv4) ethernet.getPayload();
+
+        ConnectPoint connectPoint = pkt.receivedFrom();
+        DeviceId deviceId = connectPoint.deviceId();
+        Ip4Address destinationAddress =
+                Ip4Address.valueOf(ipv4.getDestinationAddress());
+        Ip4Address gatewayIpAddress = config.getGatewayIpAddress(deviceId);
+        IpPrefix routerIpPrefix = config.getRouterIpAddress(deviceId);
+        Ip4Address routerIpAddress = routerIpPrefix.getIp4Prefix().address();
+
+        // ICMP to the router IP or gateway IP
+        if (((ICMP) ipv4.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST &&
+                (destinationAddress.equals(routerIpAddress) ||
+                        gatewayIpAddress.equals(destinationAddress))) {
+            sendICMPResponse(ethernet, connectPoint);
+            // TODO: do we need to set the flow rule again ??
+
+        // ICMP for any known host
+        } else if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
+            srManager.ipHandler.forwardPackets(deviceId, destinationAddress);
+
+        // ICMP for an unknown host in the subnet of the router
+        } else if (config.inSameSubnet(deviceId, destinationAddress)) {
+            srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, connectPoint);
+
+        // ICMP for an unknown host
+        } else {
+            log.debug("ICMP request for unknown host {} ", destinationAddress);
+            // Do nothing
+        }
+    }
+
+    private void sendICMPResponse(Ethernet icmpRequest, ConnectPoint outport) {
+
+        Ethernet icmpReplyEth = new Ethernet();
+
+        IPv4 icmpRequestIpv4 = (IPv4) icmpRequest.getPayload();
+        IPv4 icmpReplyIpv4 = new IPv4();
+
+        int destAddress = icmpRequestIpv4.getDestinationAddress();
+        icmpReplyIpv4.setDestinationAddress(icmpRequestIpv4.getSourceAddress());
+        icmpReplyIpv4.setSourceAddress(destAddress);
+        icmpReplyIpv4.setTtl((byte) 64);
+        icmpReplyIpv4.setChecksum((short) 0);
+
+        ICMP icmpReply = (ICMP) icmpRequestIpv4.getPayload().clone();
+        icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY);
+        icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY);
+        icmpReply.setChecksum((short) 0);
+
+        icmpReplyIpv4.setPayload(icmpReply);
+
+        icmpReplyEth.setPayload(icmpReplyIpv4);
+        icmpReplyEth.setEtherType(Ethernet.TYPE_IPV4);
+        icmpReplyEth.setDestinationMACAddress(icmpRequest.getSourceMACAddress());
+        icmpReplyEth.setSourceMACAddress(icmpRequest.getDestinationMACAddress());
+        icmpReplyEth.setVlanID(icmpRequest.getVlanID());
+
+        Ip4Address destIpAddress = Ip4Address.valueOf(icmpReplyIpv4.getDestinationAddress());
+        Ip4Address destRouterAddress = config.getDestinationRouterAddress(destIpAddress);
+        int sid = config.getMplsId(destRouterAddress);
+        if (sid < 0) {
+            log.warn("Cannot find the Segment ID for {}", destAddress);
+            return;
+        }
+
+        sendPacketOut(outport, icmpReplyEth, sid);
+
+    }
+
+    private void sendPacketOut(ConnectPoint outport, Ethernet payload, int sid) {
+
+        IPv4 ipPacket = (IPv4) payload.getPayload();
+        Ip4Address destIpAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress());
+
+        if (sid == -1 || config.getMplsId(payload.getDestinationMAC()) == sid ||
+                config.inSameSubnet(outport.deviceId(), destIpAddress)) {
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder().
+                    setOutput(outport.port()).build();
+            OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
+                    treatment, ByteBuffer.wrap(payload.serialize()));
+            srManager.packetService.emit(packet);
+        } else {
+            log.warn("Send a MPLS packet as a ICMP response");
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setOutput(outport.port())
+                    .build();
+
+            payload.setEtherType(Ethernet.MPLS_UNICAST);
+            MPLS mplsPkt = new MPLS();
+            mplsPkt.setLabel(sid);
+            mplsPkt.setTtl(((IPv4) payload.getPayload()).getTtl());
+            mplsPkt.setPayload(payload.getPayload());
+            payload.setPayload(mplsPkt);
+
+            OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
+                    treatment, ByteBuffer.wrap(payload.serialize()));
+
+            srManager.packetService.emit(packet);
+        }
+    }
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
new file mode 100644
index 0000000..cca8692
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class IpHandler {
+
+    private static Logger log = LoggerFactory.getLogger(IpHandler.class);
+    private SegmentRoutingManager srManager;
+    private NetworkConfigHandler config;
+    private ConcurrentHashMap<Ip4Address, ConcurrentLinkedQueue<IPv4>> ipPacketQueue;
+
+    /**
+     * Creates an IpHandler object.
+     *
+     * @param srManager SegmentRoutingManager object
+     */
+    public IpHandler(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+        this.config = checkNotNull(srManager.networkConfigHandler);
+        ipPacketQueue = new ConcurrentHashMap<Ip4Address, ConcurrentLinkedQueue<IPv4>>();
+    }
+
+    /**
+     * Processes incoming IP packets.
+     *
+     * If it is an IP packet for known host, then forward it to the host.
+     * If it is an IP packet for unknown host in subnet, then send an ARP request
+     * to the subnet.
+     *
+     * @param pkt incoming packet
+     */
+    public void processPacketIn(InboundPacket pkt) {
+        Ethernet ethernet = pkt.parsed();
+        IPv4 ipv4 = (IPv4) ethernet.getPayload();
+
+        ConnectPoint connectPoint = pkt.receivedFrom();
+        DeviceId deviceId = connectPoint.deviceId();
+        Ip4Address destinationAddress =
+                Ip4Address.valueOf(ipv4.getDestinationAddress());
+
+        // IP packet for know hosts
+        if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
+            forwardPackets(deviceId, destinationAddress);
+
+        // IP packet for unknown host in the subnet of the router
+        } else if (config.inSameSubnet(deviceId, destinationAddress)) {
+            srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, connectPoint);
+
+        // IP packets for unknown host
+        } else {
+            log.debug("ICMP request for unknown host {} which is not in the subnet",
+                    destinationAddress);
+            // Do nothing
+        }
+    }
+
+    /**
+     * Adds the IP packet to a buffer.
+     * The packets are forwarded to corresponding destination when the destination
+     * MAC address is known via ARP response.
+     *
+     * @param ipPacket IP packet to add to the buffer
+     */
+    public void addToPacketBuffer(IPv4 ipPacket) {
+
+        // Better not buffer TPC packets due to out-of-order packet transfer
+        if (ipPacket.getProtocol() == IPv4.PROTOCOL_TCP) {
+            return;
+        }
+
+        Ip4Address destIpAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress());
+
+        if (ipPacketQueue.get(destIpAddress) == null) {
+            ConcurrentLinkedQueue<IPv4> queue = new ConcurrentLinkedQueue<IPv4>();
+            queue.add(ipPacket);
+            ipPacketQueue.put(destIpAddress, queue);
+        } else {
+            ipPacketQueue.get(destIpAddress).add(ipPacket);
+        }
+    }
+
+    /**
+     * Forwards IP packets in the buffer to the destination IP address.
+     * It is called when the controller finds the destination MAC address
+     * via ARP responsees.
+     *
+     * @param deviceId switch device ID
+     * @param destIpAddress destination IP address
+     */
+    public void forwardPackets(DeviceId deviceId, Ip4Address destIpAddress) {
+        for (IPv4 ipPacket : ipPacketQueue.get(destIpAddress)) {
+            Ip4Address destAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress());
+            if (ipPacket != null && config.inSameSubnet(deviceId, destAddress)) {
+                ipPacket.setTtl((byte) (ipPacket.getTtl() - 1));
+                ipPacket.setChecksum((short) 0);
+                for (Host dest: srManager.hostService.getHostsByIp(destIpAddress)) {
+                    Ethernet eth = new Ethernet();
+                    eth.setDestinationMACAddress(dest.mac());
+                    eth.setSourceMACAddress(config.getRouterMacAddress(
+                            deviceId));
+                    eth.setEtherType(Ethernet.TYPE_IPV4);
+                    eth.setPayload(ipPacket);
+
+                    TrafficTreatment treatment = DefaultTrafficTreatment.builder().
+                            setOutput(dest.location().port()).build();
+                    OutboundPacket packet = new DefaultOutboundPacket(deviceId,
+                            treatment, ByteBuffer.wrap(eth.serialize()));
+                    srManager.packetService.emit(packet);
+                }
+            }
+        }
+    }
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java
new file mode 100644
index 0000000..2cb5867
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting;
+
+import com.google.common.collect.Lists;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class is temporary class and used only for test.
+ * It will be replaced with "real" Network Config Manager.
+ */
+
+public class NetworkConfigHandler {
+
+    private static Logger log = LoggerFactory.getLogger(NetworkConfigHandler.class);
+    private SegmentRoutingManager srManager;
+    private DeviceConfiguration deviceConfig = new DeviceConfiguration();
+
+    public NetworkConfigHandler(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+    }
+
+    public Ip4Address getGatewayIpAddress(DeviceId deviceId) {
+
+        if (deviceId.uri().equals(URI.create("of:0000000000000001"))) {
+            return Ip4Address.valueOf("10.0.1.128");
+        } else if (deviceId.uri().equals(URI.create("of:0000000000000006"))) {
+            return Ip4Address.valueOf("7.7.7.128");
+        }
+
+        log.warn("No gateway Ip address was found for {}", deviceId);
+        return Ip4Address.valueOf("0.0.0.0");
+    }
+
+    public IpPrefix getRouterIpAddress(DeviceId deviceId) {
+
+        return IpPrefix.valueOf(deviceConfig.getRouterIp(deviceId), 32);
+    }
+
+    public MacAddress getRouterMacAddress(DeviceId deviceId) {
+        return deviceConfig.getDeviceMac(deviceId);
+    }
+
+    public boolean inSameSubnet(DeviceId deviceId, Ip4Address destIp) {
+
+        String subnetInfo = getSubnetInfo(deviceId);
+        if (subnetInfo == null) {
+            return false;
+        }
+
+        IpPrefix prefix = IpPrefix.valueOf(subnetInfo);
+        if (prefix.contains(destIp)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public boolean inSameSubnet(Ip4Address address, int sid) {
+        DeviceId deviceId = deviceConfig.getDeviceId(sid);
+        if (deviceId == null) {
+            log.warn("Cannot find a device for SID {}", sid);
+            return false;
+        }
+
+        String subnetInfo = getSubnetInfo(deviceId);
+        if (subnetInfo == null) {
+            log.warn("Cannot find the subnet info for {}", deviceId);
+            return false;
+        }
+
+        Ip4Prefix subnet = Ip4Prefix.valueOf(subnetInfo);
+        if (subnet.contains(address)) {
+            return true;
+        }
+
+        return false;
+
+    }
+
+    public String getSubnetInfo(DeviceId deviceId) {
+        // TODO : supports multiple subnet
+        if (deviceId.uri().equals(URI.create("of:0000000000000001"))) {
+            return "10.0.1.1/24";
+        } else if (deviceId.uri().equals(URI.create("of:0000000000000006"))) {
+            return "7.7.7.7/24";
+        } else {
+            log.error("Switch {} is not an edge router", deviceId);
+            return null;
+        }
+    }
+
+    public int getMplsId(DeviceId deviceId) {
+        return deviceConfig.getSegmentId(deviceId);
+    }
+
+    public int getMplsId(MacAddress mac) {
+        return deviceConfig.getSegmentId(mac);
+    }
+
+    public int getMplsId(Ip4Address address) {
+        return deviceConfig.getSegmentId(address);
+    }
+
+    public boolean isEcmpNotSupportedInTransit(DeviceId deviceId) {
+        return false;
+    }
+
+    public boolean isTransitRouter(DeviceId deviceId) {
+        return true;
+    }
+
+
+    public boolean isEdgeRouter(DeviceId deviceId) {
+        if (deviceId.uri().equals(URI.create("of:0000000000000001"))
+                || deviceId.uri().equals(URI.create("of:0000000000000006"))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private List<PortNumber> getPortsToNeighbors(DeviceId deviceId, List<DeviceId> fwdSws) {
+
+        List<PortNumber> portNumbers = Lists.newArrayList();
+
+        Set<Link> links = srManager.linkService.getDeviceEgressLinks(deviceId);
+        for (Link link: links) {
+            for (DeviceId swId: fwdSws) {
+                if (link.dst().deviceId().equals(swId)) {
+                    portNumbers.add(link.src().port());
+                    break;
+                }
+            }
+        }
+
+        return portNumbers;
+    }
+
+    public List<PortNumber> getPortsToDevice(DeviceId deviceId) {
+        List<PortNumber> portNumbers = Lists.newArrayList();
+
+        Set<Link> links = srManager.linkService.getDeviceEgressLinks(deviceId);
+        for (Link link: links) {
+            if (link.dst().deviceId().equals(deviceId)) {
+                portNumbers.add(link.src().port());
+            }
+        }
+
+        return portNumbers;
+    }
+
+
+    public Ip4Address getDestinationRouterAddress(Ip4Address destIpAddress) {
+        // TODO: need to check the subnet info
+        if (destIpAddress.toString().equals("10.0.1.1")) {
+            return Ip4Address.valueOf("192.168.0.1");
+        } else if (destIpAddress.toString().equals("7.7.7.7")) {
+            return Ip4Address.valueOf("192.168.0.6");
+        } else {
+            log.warn("No router was found for {}", destIpAddress);
+            return null;
+        }
+
+    }
+
+    public DeviceId getDeviceId(Ip4Address ip4Address) {
+        return deviceConfig.getDeviceId(ip4Address);
+    }
+
+    public MacAddress getRouterMac(Ip4Address targetAddress) {
+        if (targetAddress.toString().equals("10.0.1.128")) {
+            return MacAddress.valueOf("00:00:00:00:00:01");
+        } else if (targetAddress.toString().equals("7.7.7.128")) {
+            return MacAddress.valueOf("00:00:00:00:00:06");
+        } else {
+            log.warn("Cannot find a router for {}", targetAddress);
+            return null;
+        }
+    }
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
new file mode 100644
index 0000000..063517e
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+
+import org.onlab.packet.MplsLabel;
+import org.onosproject.grouphandler.NeighborSet;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class RoutingRulePopulator {
+
+    private static final Logger log = LoggerFactory.getLogger(RoutingRulePopulator.class);
+
+    private SegmentRoutingManager srManager;
+    private NetworkConfigHandler config;
+
+    /**
+     * Creates a RoutingRulePopulator object.
+     *
+     * @param srManager
+     */
+    public RoutingRulePopulator(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+        this.config = checkNotNull(srManager.networkConfigHandler);
+    }
+
+    /**
+     * Populates IP flow rules for specific hosts directly connected to the switch.
+     *
+     * @param deviceId switch ID to set the rules
+     * @param hostIp host IP address
+     * @param hostMac host MAC address
+     * @param outPort port where the host is connected
+     */
+    public void populateIpRuleForHost(DeviceId deviceId, Ip4Address hostIp,
+                                      MacAddress hostMac, PortNumber outPort) {
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+
+        sbuilder.matchIPDst(IpPrefix.valueOf(hostIp, 32));
+        sbuilder.matchEthType(Ethernet.TYPE_IPV4);
+
+        tbuilder.setEthDst(hostMac)
+                .setEthSrc(config.getRouterMacAddress(deviceId))
+                .setOutput(outPort);
+
+        TrafficTreatment treatment = tbuilder.build();
+        TrafficSelector selector = sbuilder.build();
+
+        FlowRule f = new DefaultFlowRule(deviceId, selector, treatment, 100,
+                srManager.appId, 600, false, FlowRule.Type.IP);
+
+        srManager.flowRuleService.applyFlowRules(f);
+        log.debug("Flow rule {} is set to switch {}", f, deviceId);
+    }
+
+    /**
+     * Populates IP flow rules for the subnets of the destination router.
+     *
+     * @param deviceId switch ID to set the rules
+     * @param subnetInfo subnet information
+     * @param destSw destination switch ID
+     * @param nextHops next hop switch ID list
+     * @return true if all rules are set successfully, false otherwise
+     */
+    public boolean populateIpRuleForSubnet(DeviceId deviceId, String subnetInfo,
+                                           DeviceId destSw, Set<DeviceId> nextHops) {
+
+        List<IpPrefix> subnets = extractSubnet(subnetInfo);
+        for (IpPrefix subnet: subnets) {
+            if (!populateIpRuleForRouter(deviceId, subnet, destSw, nextHops)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Populates IP flow rules for the router IP address.
+     *
+     * @param deviceId device ID to set the rules
+     * @param ipPrefix the IP address of the destination router
+     * @param destSw device ID of the destination router
+     * @param nextHops next hop switch ID list
+     * @return true if all rules are set successfully, false otherwise
+     */
+    public boolean populateIpRuleForRouter(DeviceId deviceId, IpPrefix ipPrefix,
+                                           DeviceId destSw, Set<DeviceId> nextHops) {
+
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+
+        sbuilder.matchIPDst(ipPrefix);
+        sbuilder.matchEthType(Ethernet.TYPE_IPV4);
+
+        NeighborSet ns = null;
+
+        //If the next hop is the same as the final destination, then MPLS label is not set.
+        if (nextHops.size() == 1 && nextHops.toArray()[0].equals(destSw)) {
+            tbuilder.decNwTtl();
+            ns = new NeighborSet(nextHops);
+        } else {
+            tbuilder.copyTtlOut();
+            ns = new NeighborSet(nextHops, config.getMplsId(destSw));
+        }
+
+        DefaultGroupKey groupKey = (DefaultGroupKey) srManager.getGroupKey(ns);
+        if (groupKey == null) {
+            log.warn("Group key is not found for ns {}", ns);
+            return false;
+        }
+        Group group = srManager.groupService.getGroup(deviceId, groupKey);
+        if (group != null) {
+            tbuilder.group(group.id());
+        } else {
+            log.warn("No group found for NeighborSet {} from {} to {}",
+                    ns, deviceId, destSw);
+            return false;
+        }
+
+        TrafficTreatment treatment = tbuilder.build();
+        TrafficSelector selector = sbuilder.build();
+
+        FlowRule f = new DefaultFlowRule(deviceId, selector, treatment, 100,
+                srManager.appId, 600, false, FlowRule.Type.IP);
+
+        srManager.flowRuleService.applyFlowRules(f);
+        log.debug("IP flow rule {} is set to switch {}", f, deviceId);
+
+        return true;
+    }
+
+
+    /**
+     * Populates MPLS flow rules to all transit routers.
+     *
+     * @param deviceId device ID of the switch to set the rules
+     * @param destSwId destination switch device ID
+     * @param nextHops next hops switch ID list
+     * @return true if all rules are set successfully, false otherwise
+     */
+    public boolean populateMplsRule(DeviceId deviceId, DeviceId destSwId, Set<DeviceId> nextHops) {
+
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        Collection<TrafficTreatment> treatments = new ArrayList<>();
+
+        // TODO Handle the case of Bos == false
+        sbuilder.matchMplsLabel(MplsLabel.mplsLabel(config.getMplsId(destSwId)));
+        sbuilder.matchEthType(Ethernet.MPLS_UNICAST);
+
+        //If the next hop is the destination router, do PHP
+        if (nextHops.size() == 1 && destSwId.equals(nextHops.toArray()[0])) {
+            TrafficTreatment treatmentBos =
+                    getMplsTreatment(deviceId, destSwId, nextHops, true, true);
+            TrafficTreatment treatment =
+                    getMplsTreatment(deviceId, destSwId, nextHops, true, false);
+            if (treatmentBos != null) {
+                treatments.add(treatmentBos);
+            } else {
+                log.warn("Failed to set MPLS rules.");
+                return false;
+            }
+        } else {
+            TrafficTreatment treatmentBos =
+                    getMplsTreatment(deviceId, destSwId, nextHops, false, true);
+            TrafficTreatment treatment =
+                    getMplsTreatment(deviceId, destSwId, nextHops, false, false);
+
+            if (treatmentBos != null) {
+                treatments.add(treatmentBos);
+            } else {
+                log.warn("Failed to set MPLS rules.");
+                return false;
+            }
+        }
+
+        TrafficSelector selector = sbuilder.build();
+        for (TrafficTreatment treatment: treatments) {
+            FlowRule f = new DefaultFlowRule(deviceId, selector, treatment, 100,
+                    srManager.appId, 600, false, FlowRule.Type.MPLS);
+            srManager.flowRuleService.applyFlowRules(f);
+            log.debug("MPLS rule {} is set to {}", f, deviceId);
+        }
+
+        return true;
+    }
+
+
+    private TrafficTreatment getMplsTreatment(DeviceId deviceId, DeviceId destSw,
+                                             Set<DeviceId> nextHops,
+                                             boolean phpRequired, boolean isBos) {
+
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+
+        if (phpRequired) {
+            tbuilder.copyTtlIn();
+            if (isBos) {
+                tbuilder.popMpls(Ethernet.TYPE_IPV4)
+                        .decNwTtl();
+            } else {
+                tbuilder.popMpls(Ethernet.MPLS_UNICAST)
+                .decMplsTtl();
+            }
+        } else {
+            tbuilder.decMplsTtl();
+        }
+
+        if (config.isEcmpNotSupportedInTransit(deviceId)
+                && config.isTransitRouter(deviceId)) {
+            Link link = selectOneLink(deviceId, nextHops);
+            if (link == null) {
+                log.warn("No link from {} to {}", deviceId, nextHops);
+                return null;
+            }
+            tbuilder.setEthSrc(config.getRouterMacAddress(deviceId))
+                    .setEthDst(config.getRouterMacAddress(link.dst().deviceId()))
+                    .setOutput(link.src().port());
+        } else {
+            NeighborSet ns = new NeighborSet(nextHops);
+            DefaultGroupKey groupKey = (DefaultGroupKey) srManager.getGroupKey(ns);
+            if (groupKey == null) {
+                log.warn("Group key is not found for ns {}", ns);
+                return null;
+            }
+            Group group = srManager.groupService.getGroup(deviceId, groupKey);
+            if (group != null) {
+                tbuilder.group(group.id());
+            } else {
+                log.warn("No group found for ns {} key {} in {}", ns,
+                        srManager.getGroupKey(ns), deviceId);
+                return null;
+            }
+        }
+
+        return tbuilder.build();
+    }
+
+    /**
+     * Populates VLAN flows rules.
+     * All packets are forwarded to TMAC table.
+     *
+     * @param deviceId switch ID to set the rules
+     */
+    public void populateTableVlan(DeviceId deviceId) {
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+
+        tbuilder.transition(FlowRule.Type.ETHER);
+
+        TrafficTreatment treatment = tbuilder.build();
+        TrafficSelector selector = sbuilder.build();
+
+        FlowRule f = new DefaultFlowRule(deviceId, selector, treatment, 100,
+                srManager.appId, 600, false, FlowRule.Type.VLAN);
+
+        srManager.flowRuleService.applyFlowRules(f);
+
+        log.debug("Vlan flow rule {} is set to switch {}", f, deviceId);
+    }
+
+    /**
+     * Populates TMAC table rules.
+     * IP packets are forwarded to IP table.
+     * MPLS packets are forwarded to MPLS table.
+     *
+     * @param deviceId switch ID to set the rules
+     */
+    public void populateTableTMac(DeviceId deviceId) {
+
+        // flow rule for IP packets
+        TrafficSelector selectorIp = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchEthDst(config.getRouterMacAddress(deviceId))
+                .build();
+        TrafficTreatment treatmentIp = DefaultTrafficTreatment.builder()
+                .transition(FlowRule.Type.IP)
+                .build();
+
+        FlowRule flowIp = new DefaultFlowRule(deviceId, selectorIp, treatmentIp, 100,
+                srManager.appId, 600, false, FlowRule.Type.ETHER);
+
+        srManager.flowRuleService.applyFlowRules(flowIp);
+
+        // flow rule for MPLS packets
+        TrafficSelector selectorMpls = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.MPLS_UNICAST)
+                .matchEthDst(config.getRouterMacAddress(deviceId))
+                .build();
+        TrafficTreatment treatmentMpls = DefaultTrafficTreatment.builder()
+                .transition(FlowRule.Type.MPLS)
+                .build();
+
+        FlowRule flowMpls = new DefaultFlowRule(deviceId, selectorMpls, treatmentMpls, 100,
+                srManager.appId, 600, false, FlowRule.Type.ETHER);
+
+        srManager.flowRuleService.applyFlowRules(flowMpls);
+
+    }
+
+    /**
+     * Populates a table miss entry.
+     *
+     * @param deviceId switch ID to set rules
+     * @param tableToAdd table to set the rules
+     * @param toControllerNow flag to send packets to controller immediately
+     * @param toControllerWrite flag to send packets to controller at the end of pipeline
+     * @param toTable flag to send packets to a specific table
+     * @param tableToSend table type to send packets when the toTable flag is set
+     */
+    public void populateTableMissEntry(DeviceId deviceId, FlowRule.Type tableToAdd, boolean toControllerNow,
+                                       boolean toControllerWrite,
+                                       boolean toTable, FlowRule.Type tableToSend) {
+        // TODO: Change arguments to EnumSet
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .build();
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+        if (toControllerNow) {
+            tBuilder.setOutput(PortNumber.CONTROLLER);
+        }
+
+        if (toControllerWrite) {
+            tBuilder.deferred().setOutput(PortNumber.CONTROLLER);
+        }
+
+        if (toTable) {
+            tBuilder.transition(tableToSend);
+        }
+
+        FlowRule flow = new DefaultFlowRule(deviceId, selector, tBuilder.build(), 0,
+                srManager.appId, 600, false, tableToAdd);
+
+        srManager.flowRuleService.applyFlowRules(flow);
+
+    }
+
+
+    private List<IpPrefix> extractSubnet(String subnetInfo) {
+        List<IpPrefix> subnetIpPrefixes = new ArrayList<>();
+
+        // TODO: refactoring required depending on the format of the subnet info
+        IpPrefix prefix = IpPrefix.valueOf(subnetInfo);
+        if (prefix == null) {
+            log.error("Wrong ip prefix type {}", subnetInfo);
+        } else {
+            subnetIpPrefixes.add(prefix);
+        }
+
+        return subnetIpPrefixes;
+    }
+
+    private Link selectOneLink(DeviceId srcId, Set<DeviceId> destIds) {
+
+        Set<Link> links = srManager.linkService.getDeviceEgressLinks(srcId);
+        DeviceId destId = (DeviceId) destIds.toArray()[0];
+        for (Link link: links) {
+            if (link.dst().deviceId().equals(destId)) {
+                return link;
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
new file mode 100644
index 0000000..a7a3b11
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting;
+
+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.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.Event;
+import org.onosproject.grouphandler.DefaultGroupHandler;
+import org.onosproject.grouphandler.NeighborSet;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.link.LinkEvent;
+import org.onosproject.net.link.LinkListener;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.topology.TopologyService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+@SuppressWarnings("ALL")
+@Component(immediate = true)
+public class SegmentRoutingManager {
+
+    private static Logger log = LoggerFactory.getLogger(SegmentRoutingManager.class);
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected TopologyService topologyService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService linkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected GroupService groupService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    protected NetworkConfigHandler networkConfigHandler = new NetworkConfigHandler(this);
+    protected ArpHandler arpHandler = new ArpHandler(this);
+    protected IcmpHandler icmpHandler = new IcmpHandler(this);
+    protected IpHandler ipHandler = new IpHandler(this);
+    protected RoutingRulePopulator routingRulePopulator = new RoutingRulePopulator(this);
+    protected ApplicationId appId;
+
+    private DefaultRoutingHandler defaultRoutingHandler = new DefaultRoutingHandler(this);
+    private DeviceConfiguration deviceConfiguration = new DeviceConfiguration();
+    private InternalPacketProcessor processor = new InternalPacketProcessor();
+    private InternalEventHandler eventHandler = new InternalEventHandler();
+
+    private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
+
+    private static ScheduledFuture<?> eventHandlerFuture = null;
+    private ConcurrentLinkedQueue<Event> eventQueue = new ConcurrentLinkedQueue<Event>();
+    private Map<DeviceId, DefaultGroupHandler> groupHandlerMap
+            = new ConcurrentHashMap<DeviceId, DefaultGroupHandler>();
+
+    private static int numOfEvents = 0;
+    private static int numOfHandlerExecution = 0;
+    private static int numOfHandlerScheduled = 0;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication("org.onosproject.segmentrouting");
+        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);
+        linkService.addListener(new InternalLinkListener());
+        groupService.addListener(new InternalGroupListener());
+        deviceService.addListener(new InternalDeviceListener());
+
+        for (Device device: deviceService.getDevices()) {
+            if (mastershipService.
+                    getLocalRole(device.id()) == MastershipRole.MASTER) {
+                DefaultGroupHandler groupHandler =
+                    DefaultGroupHandler.createGroupHandler(device.id(),
+                            appId, deviceConfiguration, linkService, groupService);
+                groupHandler.createGroups();
+                groupHandlerMap.put(device.id(), groupHandler);
+                log.debug("Initiating default group handling for {}", device.id());
+
+                defaultRoutingHandler.startPopulationProcess();
+            } else {
+                log.debug("Activate: Local role {} "
+                                + "is not MASTER for device {}",
+                        mastershipService.
+                                getLocalRole(device.id()),
+                        device.id());
+            }
+        }
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        packetService.removeProcessor(processor);
+        processor = null;
+        log.info("Stopped");
+    }
+
+    /**
+     * Returns the GrouopKey object for the device and the NighborSet given.
+     *
+     * @param ns NeightborSet object for the GroupKey
+     * @return GroupKey object for the NeighborSet
+     */
+    public GroupKey getGroupKey(NeighborSet ns) {
+
+        for (DefaultGroupHandler groupHandler: groupHandlerMap.values()) {
+            return groupHandler.getGroupKey(ns);
+        }
+
+        return null;
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+
+            if (ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                arpHandler.processPacketIn(pkt);
+            } else if (ethernet.getEtherType() == Ethernet.TYPE_IPV4) {
+                IPv4 ipPacket = (IPv4) ethernet.getPayload();
+                ipHandler.addToPacketBuffer(ipPacket);
+                if (ipPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
+                    icmpHandler.processPacketIn(pkt);
+                } else {
+                    ipHandler.processPacketIn(pkt);
+                }
+            }
+        }
+    }
+
+    private class InternalLinkListener implements LinkListener {
+        @Override
+        public void event(LinkEvent event) {
+            if (event.type() == LinkEvent.Type.LINK_ADDED ||
+                    event.type() == LinkEvent.Type.LINK_REMOVED) {
+                scheduleEventHandlerIfNotScheduled(event);
+            }
+        }
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+
+        @Override
+        public void event(DeviceEvent event) {
+            if (mastershipService.
+                    getLocalRole(event.subject().id()) != MastershipRole.MASTER) {
+                log.debug("Local role {} is not MASTER for device {}",
+                        mastershipService.
+                                getLocalRole(event.subject().id()),
+                        event.subject().id());
+                return;
+            }
+
+            switch (event.type()) {
+            case DEVICE_ADDED:
+            case PORT_REMOVED:
+                scheduleEventHandlerIfNotScheduled(event);
+                break;
+            default:
+            }
+        }
+    }
+
+    private class InternalGroupListener implements GroupListener {
+
+        @Override
+        public void event(GroupEvent event) {
+            switch (event.type()) {
+                case GROUP_ADDED:
+                    scheduleEventHandlerIfNotScheduled(event);
+                    break;
+                case GROUP_ADD_REQUESTED:
+                    log.info("Group add requested");
+                    break;
+                case GROUP_UPDATED:
+                    break;
+                default:
+                    log.warn("Unhandled group event type: {}", event.type());
+            }
+        }
+    }
+
+    private void scheduleEventHandlerIfNotScheduled(Event event) {
+
+        eventQueue.add(event);
+        numOfEvents++;
+        if (eventHandlerFuture == null ||
+                eventHandlerFuture.isDone()) {
+            eventHandlerFuture = executorService.schedule(eventHandler,
+                    100, TimeUnit.MILLISECONDS);
+            numOfHandlerScheduled++;
+        }
+
+        log.trace("numOfEvents {}, numOfEventHanlderScheduled {}", numOfEvents,
+                numOfHandlerScheduled);
+
+    }
+
+    private class InternalEventHandler implements Runnable {
+
+        public void run() {
+            numOfHandlerExecution++;
+            while (!eventQueue.isEmpty()) {
+                Event event = eventQueue.poll();
+                if (event.type() == LinkEvent.Type.LINK_ADDED) {
+                    processLinkAdded((Link) event.subject());
+                } else if (event.type() == LinkEvent.Type.LINK_REMOVED) {
+                    processLinkRemoved((Link) event.subject());
+                } else if (event.type() == GroupEvent.Type.GROUP_ADDED) {
+                    processGroupAdded((Group) event.subject());
+                } else if (event.type() == DeviceEvent.Type.DEVICE_ADDED) {
+                    processDeviceAdded((Device) event.subject());
+                } else if (event.type() == DeviceEvent.Type.PORT_REMOVED) {
+                    processPortRemoved((Device) event.subject(),
+                            ((DeviceEvent) event).port());
+                } else {
+                    log.warn("Unhandled event type: {}", event.type());
+                }
+            }
+            log.debug("numOfHandlerExecution {} numOfEventHanlderScheduled {} numOfEvents {}",
+                    numOfHandlerExecution, numOfHandlerScheduled, numOfEvents);
+        }
+    }
+
+
+
+    private void processLinkAdded(Link link) {
+        log.debug("A new link {} was added", link.toString());
+
+        if (mastershipService.
+                getLocalRole(link.src().deviceId()) == MastershipRole.MASTER) {
+            DefaultGroupHandler groupHandler =
+                    groupHandlerMap.get(link.src().deviceId());
+            if (groupHandler != null) {
+                groupHandler.linkUp(link);
+            }
+        }
+        defaultRoutingHandler.startPopulationProcess();
+    }
+
+    private void processLinkRemoved(Link link) {
+        log.debug("A link {} was removed", link.toString());
+        defaultRoutingHandler.startPopulationProcess();
+    }
+
+
+    private void processGroupAdded(Group group) {
+        log.debug("A new group with ID {} was added", group.id());
+        defaultRoutingHandler.resumePopulationProcess();
+    }
+
+    private void processDeviceAdded(Device device) {
+        log.debug("A new device with ID {} was added", device.id());
+        defaultRoutingHandler.populateTtpRules(device.id());
+        DefaultGroupHandler dgh = DefaultGroupHandler.createGroupHandler(
+                device.id(), appId, new DeviceConfiguration(), linkService, groupService);
+        dgh.createGroups();
+        groupHandlerMap.put(device.id(), dgh);
+    }
+
+    private void processPortRemoved(Device device, Port port) {
+        log.debug("Port {} was removed", port.toString());
+        DefaultGroupHandler groupHandler =
+                groupHandlerMap.get(device.id());
+        if (groupHandler != null) {
+            groupHandler.portDown(port.number());
+        }
+    }
+}
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 73a0e00..5672c03 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -345,7 +345,8 @@
                     MplsLabelResourceAllocation.class,
                     MplsLabelResourceRequest.class,
                     MplsLabel.class,
-                    org.onlab.packet.MplsLabel.class
+                    org.onlab.packet.MplsLabel.class,
+                    org.onlab.packet.MPLS.class
                     )
 
             .build();
diff --git a/features/features.xml b/features/features.xml
index a132cf2..c30d272 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -285,4 +285,18 @@
         <bundle>mvn:org.onosproject/onos-app-calendar/@ONOS-VERSION</bundle>
     </feature>
 
+    <feature name="onos-app-grouphandler" version="@FEATURE-VERSION"
+             description="Group Handler Sample App">
+        <feature>onos-api</feature>
+        <bundle>mvn:org.onosproject/onos-app-grouphandler/@ONOS-VERSION</bundle>
+    </feature>
+
+    <feature name="onos-app-segmentrouting" version="@FEATURE-VERSION"
+             description="Segment routing application">
+        <feature>onos-api</feature>
+        <bundle>mvn:org.onosproject/onos-app-segmentrouting/@ONOS-VERSION</bundle>
+        <bundle>mvn:org.onosproject/onos-app-grouphandler/@ONOS-VERSION</bundle>
+    </feature>
+
+
 </features>
diff --git a/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/DriverManager.java b/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/DriverManager.java
index bfd60d9..ec8bf31 100644
--- a/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/DriverManager.java
+++ b/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/DriverManager.java
@@ -70,7 +70,7 @@
         if (vendor.startsWith("Stanford University, Ericsson Research and CPqD Research")
                 &&
                 hw.startsWith("OpenFlow 1.3 Reference Userspace Switch")) {
-            return new OFSwitchImplCPqD13(dpid, desc);
+            return new OFSwitchImplSpringOpenTTP(dpid, desc);
         }
 
         if (hw.startsWith("Open vSwitch")) {
diff --git a/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/OFSwitchImplSpringOpenTTP.java b/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/OFSwitchImplSpringOpenTTP.java
index 963529c..4e7f61d 100644
--- a/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/OFSwitchImplSpringOpenTTP.java
+++ b/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/OFSwitchImplSpringOpenTTP.java
@@ -15,43 +15,21 @@
  */
 package org.onosproject.openflow.drivers;
 
+import com.google.common.collect.Lists;
 import org.onosproject.openflow.controller.Dpid;
-import org.onosproject.openflow.controller.RoleState;
-import org.onosproject.openflow.controller.OpenFlowSwitch.TableType;
 import org.onosproject.openflow.controller.driver.AbstractOpenFlowSwitch;
 import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeAlreadyStarted;
 import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeCompleted;
 import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeNotStarted;
-import org.projectfloodlight.openflow.protocol.OFAsyncGetReply;
-import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
-import org.projectfloodlight.openflow.protocol.OFErrorMsg;
-import org.projectfloodlight.openflow.protocol.OFGroupDescStatsReply;
-import org.projectfloodlight.openflow.protocol.OFGroupFeaturesStatsReply;
-import org.projectfloodlight.openflow.protocol.OFMatchV3;
-import org.projectfloodlight.openflow.protocol.OFOxmList;
-import org.projectfloodlight.openflow.protocol.OFPortDesc;
-import org.projectfloodlight.openflow.protocol.OFStatsReply;
 import org.projectfloodlight.openflow.protocol.OFFlowMod;
 import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
 import org.projectfloodlight.openflow.protocol.OFFactory;
 import org.projectfloodlight.openflow.protocol.OFMessage;
 import org.projectfloodlight.openflow.protocol.OFType;
-import org.projectfloodlight.openflow.protocol.action.OFAction;
 import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
-import org.projectfloodlight.openflow.protocol.oxm.OFOxmEthType;
-import org.projectfloodlight.openflow.protocol.oxm.OFOxmInPort;
-import org.projectfloodlight.openflow.protocol.oxm.OFOxmVlanVid;
-import org.projectfloodlight.openflow.types.EthType;
-import org.projectfloodlight.openflow.types.MacAddress;
-import org.projectfloodlight.openflow.types.OFBufferId;
-import org.projectfloodlight.openflow.types.OFPort;
-import org.projectfloodlight.openflow.types.OFVlanVidMatch;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstructionGotoTable;
 import org.projectfloodlight.openflow.types.TableId;
-import org.projectfloodlight.openflow.types.U32;
-import org.projectfloodlight.openflow.util.HexString;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -63,8 +41,6 @@
     private final AtomicBoolean driverHandshakeComplete;
     private AtomicBoolean haltStateMachine;
 
-    private DriverState driverState;
-
     /* Default table ID - compatible with CpqD switch */
     private static final int TABLE_VLAN = 0;
     private static final int TABLE_TMAC = 1;
@@ -72,14 +48,6 @@
     private static final int TABLE_MPLS = 3;
     private static final int TABLE_ACL = 5;
 
-    private static final long TEST_FLOW_REMOVED_MASK = 0xf;
-    private static final long TEST_PACKET_IN_MASK = 0x7;
-    private static final long TEST_PORT_STATUS_MASK = 0x7;
-
-    private static final int OFPCML_NO_BUFFER = 0xffff;
-
-    private long barrierXidToWaitFor = -1;
-
     /* Set the default values. These variables will get
      * overwritten based on the switch vendor type
      */
@@ -89,17 +57,10 @@
     protected int mplsTableId = TABLE_MPLS;
     protected int aclTableId = TABLE_ACL;
 
-    /* priority values for OF message */
-    private static final short MAX_PRIORITY = (short) 0xffff;
-    private static final short PRIORITY_MULTIPLIER = (short) 2046;
-    private static final short MIN_PRIORITY = 0x0;
-
-
     protected OFSwitchImplSpringOpenTTP(Dpid dpid, OFDescStatsReply desc) {
         super(dpid);
         driverHandshakeComplete = new AtomicBoolean(false);
         haltStateMachine = new AtomicBoolean(false);
-        driverState = DriverState.INIT;
         setSwitchDescription(desc);
     }
 
@@ -117,6 +78,7 @@
         return null;
     }
 
+
     @Override
     public void startDriverHandshake() {
         log.debug("Starting driver handshake for sw {}", getStringId());
@@ -126,12 +88,9 @@
         startDriverHandshakeCalled = true;
         factory = this.factory();
 
-        try {
-            nextDriverState();
-        } catch (IOException e) {
-            log.error("Error {} during driver handshake for sw {}", e.getCause(),
-                    getStringId());
-        }
+        driverHandshakeComplete.set(true);
+        log.debug("Driver handshake is complete");
+
     }
 
     @Override
@@ -142,6 +101,8 @@
         return driverHandshakeComplete.get();
     }
 
+
+
     @Override
     public void processDriverHandshakeMessage(OFMessage m) {
         if (!startDriverHandshakeCalled) {
@@ -150,13 +111,9 @@
         if (driverHandshakeComplete.get()) {
             throw new SwitchDriverSubHandshakeCompleted(m);
         }
-        try {
-            processOFMessage(m);
-        } catch (IOException e) {
-            log.error("Error generated when processing OFMessage {}", e.getCause());
-        }
     }
 
+
     @Override
     public void write(OFMessage msg) {
         this.channel.write(Collections.singletonList(msg));
@@ -168,384 +125,51 @@
     }
 
     @Override
-    public void transformAndSendMsg(OFMessage m, TableType tableType) {
-
-        if (m.getType() == OFType.FLOW_MOD) {
-            OFFlowMod flowMod = (OFFlowMod) m;
+    public void transformAndSendMsg(OFMessage msg, TableType type) {
+        if (msg.getType() == OFType.FLOW_MOD) {
+            OFFlowMod flowMod = (OFFlowMod) msg;
             OFFlowMod.Builder builder = flowMod.createBuilder();
-            builder.setTableId(getTableId(tableType));
-            OFFlowMod newFlowMod = builder.build();
-            if (role == RoleState.MASTER) {
-                this.write(newFlowMod);
-            }
-        } else {
-            if (role == RoleState.MASTER) {
-                this.write(m);
-            }
-        }
-    }
-
-    /*
-     * Driver handshake state machine
-     */
-
-    enum DriverState {
-        INIT,
-        SET_TABLE_MISS_ENTRIES,
-        SET_TABLE_VLAN_TMAC,
-        AUDIT_GROUPS,
-        SET_GROUPS,
-        VERIFY_GROUPS,
-        SET_ADJACENCY_LABELS,
-        EXIT
-    }
-
-    protected void nextDriverState() throws IOException {
-        DriverState currentState = driverState;
-        if (haltStateMachine.get()) {
-            return;
-        }
-        switch (currentState) {
-            case INIT:
-                driverState = DriverState.SET_TABLE_MISS_ENTRIES;
-                setTableMissEntries();
-                sendHandshakeBarrier();
-                break;
-            case SET_TABLE_MISS_ENTRIES:
-                driverState = DriverState.SET_TABLE_VLAN_TMAC;
-                /* TODO: read network configuration
-                boolean isConfigured = getNetworkConfig();
-                if (!isConfigured) {
-                    return; // this will result in a handshake timeout
+            List<OFInstruction> instructions = flowMod.getInstructions();
+            List<OFInstruction> newInstructions = Lists.newArrayList();
+            for (OFInstruction i : instructions) {
+                if (i instanceof OFInstructionGotoTable) {
+                    OFInstructionGotoTable gotoTable = (OFInstructionGotoTable) i;
+                    TableType tid = TableType.values()[gotoTable.getTableId().getValue()];
+                    newInstructions.add(
+                            gotoTable.createBuilder()
+                                    .setTableId(getTableId(tid)).build());
+                } else {
+                    newInstructions.add(i);
                 }
-                */
-                populateTableVlan();
-                populateTableTMac();
-                sendHandshakeBarrier();
-                break;
-            case SET_TABLE_VLAN_TMAC:
-                driverState = DriverState.EXIT;
-                driverHandshakeComplete.set(true);
-                log.debug("Driver handshake is complete");
-                break;
-            case EXIT:
-            default:
-                driverState = DriverState.EXIT;
-                log.error("Driver handshake has exited for sw: {}", getStringId());
-        }
-    }
-
-    private void processStatsReply(OFStatsReply sr) {
-        switch (sr.getStatsType()) {
-            case AGGREGATE:
-                break;
-            case DESC:
-                break;
-            case EXPERIMENTER:
-                break;
-            case FLOW:
-                break;
-            case GROUP_DESC:
-                processGroupDesc((OFGroupDescStatsReply) sr);
-                break;
-            case GROUP_FEATURES:
-                processGroupFeatures((OFGroupFeaturesStatsReply) sr);
-                break;
-            case METER_CONFIG:
-                break;
-            case METER_FEATURES:
-                break;
-            case PORT_DESC:
-                break;
-            case TABLE_FEATURES:
-                break;
-            default:
-                break;
-
-        }
-    }
-
-    private void processOFMessage(OFMessage m) throws IOException {
-        switch (m.getType()) {
-            case BARRIER_REPLY:
-                processBarrierReply(m);
-                break;
-
-            case ERROR:
-                processErrorMessage(m);
-                break;
-
-            case GET_ASYNC_REPLY:
-                OFAsyncGetReply asrep = (OFAsyncGetReply) m;
-                decodeAsyncGetReply(asrep);
-                break;
-
-            case PACKET_IN:
-                // not ready to handle packet-ins
-                break;
-
-            case QUEUE_GET_CONFIG_REPLY:
-                // not doing queue config yet
-                break;
-
-            case STATS_REPLY:
-                processStatsReply((OFStatsReply) m);
-                break;
-
-            case ROLE_REPLY: // channelHandler should handle this
-            case PORT_STATUS: // channelHandler should handle this
-            case FEATURES_REPLY: // don't care
-            case FLOW_REMOVED: // don't care
-            default:
-                log.debug("Received message {} during switch-driver subhandshake "
-                        + "from switch {} ... Ignoring message", m, getStringId());
-        }
-    }
-
-    private void processBarrierReply(OFMessage m) throws IOException {
-        if (m.getXid() == barrierXidToWaitFor) {
-            // Driver state-machine progresses to the next state.
-            // If Barrier messages is not received, then eventually
-            // the ChannelHandler state machine will timeout, and the switch
-            // will be disconnected.
-            nextDriverState();
-        } else {
-            log.error("Received incorrect barrier-message xid {} (expected: {}) in "
-                            + "switch-driver state {} for switch {}", m, barrierXidToWaitFor,
-                    driverState, getStringId());
-        }
-    }
-
-    private void processErrorMessage(OFMessage m) {
-        log.error("Switch {} Error {} in DriverState", getStringId(),
-                (OFErrorMsg) m, driverState);
-    }
-
-    private void processGroupFeatures(OFGroupFeaturesStatsReply gfsr) {
-        log.info("Sw: {} Group Features {}", getStringId(), gfsr);
-    }
-
-    private void processGroupDesc(OFGroupDescStatsReply gdsr) {
-        log.info("Sw: {} Group Desc {}", getStringId(), gdsr);
-    }
-
-    /*
-     * Utility functions
-     */
-
-    private void decodeAsyncGetReply(OFAsyncGetReply rep) {
-        long frm = rep.getFlowRemovedMaskEqualMaster();
-        //long frs = rep.getFlowRemovedMaskSlave();
-        long pim = rep.getPacketInMaskEqualMaster();
-        //long pis = rep.getPacketInMaskSlave();
-        long psm = rep.getPortStatusMaskEqualMaster();
-        //long pss = rep.getPortStatusMaskSlave();
-
-        if (role == RoleState.MASTER || role == RoleState.EQUAL) { // should separate
-            log.info("FRM:{}", HexString.toHexString((frm & TEST_FLOW_REMOVED_MASK)));
-            log.info("PIM:{}", HexString.toHexString((pim & TEST_PACKET_IN_MASK)));
-            log.info("PSM:{}", HexString.toHexString((psm & TEST_PORT_STATUS_MASK)));
-        }
-    }
-
-    protected void setTableMissEntries() throws IOException {
-        // set all table-miss-entries
-        populateTableMissEntry(vlanTableId, true, false, false, -1);
-        populateTableMissEntry(tmacTableId, true, false, false, -1);
-        populateTableMissEntry(ipv4UnicastTableId, false, true, true,
-                aclTableId);
-        populateTableMissEntry(mplsTableId, false, true, true,
-                aclTableId);
-        populateTableMissEntry(aclTableId, false, false, false, -1);
-        log.debug("TableMissEntries are set");
-    }
-
-    /**
-     * Adds a table-miss-entry to a pipeline table.
-     * <p>
-     * The table-miss-entry can be added with 'write-actions' or
-     * 'apply-actions'. It can also add a 'goto-table' instruction. By default
-     * if none of the booleans in the call are set, then the table-miss entry is
-     * added with no instructions, which means that if a packet hits the
-     * table-miss-entry, pipeline execution will stop, and the action set
-     * associated with the packet will be executed.
-     *
-     * @param tableToAdd the table to where the table-miss-entry will be added
-     * @param toControllerNow as an APPLY_ACTION instruction
-     * @param toControllerWrite as a WRITE_ACTION instruction
-     * @param toTable as a GOTO_TABLE instruction
-     * @param tableToSend the table to send as per the GOTO_TABLE instruction it
-     *        needs to be set if 'toTable' is true. Ignored of 'toTable' is
-     *        false.
-     */
-    protected void populateTableMissEntry(int tableToAdd, boolean toControllerNow,
-                                          boolean toControllerWrite,
-                                          boolean toTable, int tableToSend) {
-        OFOxmList oxmList = OFOxmList.EMPTY;
-        OFMatchV3 match = factory.buildMatchV3()
-                .setOxmList(oxmList)
-                .build();
-        OFAction outc = factory.actions()
-                .buildOutput()
-                .setPort(OFPort.CONTROLLER)
-                .setMaxLen(OFPCML_NO_BUFFER)
-                .build();
-        List<OFInstruction> instructions = new ArrayList<OFInstruction>();
-        if (toControllerNow) {
-            // table-miss instruction to send to controller immediately
-            OFInstruction instr = factory.instructions()
-                    .buildApplyActions()
-                    .setActions(Collections.singletonList(outc))
-                    .build();
-            instructions.add(instr);
-        }
-
-        if (toControllerWrite) {
-            // table-miss instruction to write-action to send to controller
-            // this will be executed whenever the action-set gets executed
-            OFInstruction instr = factory.instructions()
-                    .buildWriteActions()
-                    .setActions(Collections.singletonList(outc))
-                    .build();
-            instructions.add(instr);
-        }
-
-        if (toTable) {
-            // table-miss instruction to goto-table x
-            OFInstruction instr = factory.instructions()
-                    .gotoTable(TableId.of(tableToSend));
-            instructions.add(instr);
-        }
-
-        if (!toControllerNow && !toControllerWrite && !toTable) {
-            // table-miss has no instruction - at which point action-set will be
-            // executed - if there is an action to output/group in the action
-            // set
-            // the packet will be sent there, otherwise it will be dropped.
-            instructions = Collections.<OFInstruction>emptyList();
-        }
-
-        OFMessage tableMissEntry = factory.buildFlowAdd()
-                .setTableId(TableId.of(tableToAdd))
-                .setMatch(match) // match everything
-                .setInstructions(instructions)
-                .setPriority(MIN_PRIORITY)
-                .setBufferId(OFBufferId.NO_BUFFER)
-                .setIdleTimeout(0)
-                .setHardTimeout(0)
-                .setXid(getNextTransactionId())
-                .build();
-        write(tableMissEntry);
-    }
-
-    private void populateTableVlan() throws IOException {
-        List<OFMessage> msglist = new ArrayList<OFMessage>();
-        for (OFPortDesc p : getPorts()) {
-            int pnum = p.getPortNo().getPortNumber();
-            if (U32.of(pnum).compareTo(U32.of(OFPort.MAX.getPortNumber())) < 1) {
-                OFOxmInPort oxp = factory.oxms().inPort(p.getPortNo());
-                OFOxmVlanVid oxv = factory.oxms()
-                        .vlanVid(OFVlanVidMatch.UNTAGGED);
-                OFOxmList oxmList = OFOxmList.of(oxp, oxv);
-                OFMatchV3 match = factory.buildMatchV3()
-                        .setOxmList(oxmList).build();
-
-                // TODO: match on vlan-tagged packets for vlans configured on
-                // subnet ports and strip-vlan
-
-                OFInstruction gotoTbl = factory.instructions().buildGotoTable()
-                        .setTableId(TableId.of(tmacTableId)).build();
-                List<OFInstruction> instructions = new ArrayList<OFInstruction>();
-                instructions.add(gotoTbl);
-                OFMessage flowEntry = factory.buildFlowAdd()
-                        .setTableId(TableId.of(vlanTableId))
-                        .setMatch(match)
-                        .setInstructions(instructions)
-                        .setPriority(1000) // does not matter - all rules
-                                // exclusive
-                        .setBufferId(OFBufferId.NO_BUFFER)
-                        .setIdleTimeout(0)
-                        .setHardTimeout(0)
-                        .setXid(getNextTransactionId())
-                        .build();
-                msglist.add(flowEntry);
             }
+            builder.setTableId(getTableId(type));
+            builder.setInstructions(newInstructions);
+            OFMessage msgnew = builder.build();
+            channel.write(Collections.singletonList(msgnew));
+            log.trace("Installed {}", msgnew);
+
+        } else {
+            channel.write(Collections.singletonList(msg));
         }
-        write(msglist);
-        log.debug("Adding {} port/vlan-rules in sw {}", msglist.size(), getStringId());
     }
 
-    private void populateTableTMac() throws IOException {
-        // match for router-mac and ip-packets
-        OFOxmEthType oxe = factory.oxms().ethType(EthType.IPv4);
-
-        /* TODO: need to read network config and need to allow only
-         the packets with DMAC as the correspondent router MAC address
-         Until network configuration is implemented, all packets are allowed
-
-        OFOxmEthDst dmac = factory.oxms().ethDst(getRouterMacAddr());
-        OFOxmList oxmListIp = OFOxmList.of(dmac, oxe);
-        OFMatchV3 matchIp = factory.buildMatchV3()
-                .setOxmList(oxmListIp).build();
-        */
-        OFOxmList oxmList = OFOxmList.EMPTY;
-        OFMatchV3 matchIp = factory.buildMatchV3()
-                .setOxmList(oxmList)
-                .build();
-
-        OFInstruction gotoTblIp = factory.instructions().buildGotoTable()
-                .setTableId(TableId.of(ipv4UnicastTableId)).build();
-        List<OFInstruction> instructionsIp = Collections.singletonList(gotoTblIp);
-        OFMessage ipEntry = factory.buildFlowAdd()
-                .setTableId(TableId.of(tmacTableId))
-                .setMatch(matchIp)
-                .setInstructions(instructionsIp)
-                .setPriority(1000) // strict priority required lower than
-                        // multicastMac
-                .setBufferId(OFBufferId.NO_BUFFER)
-                .setIdleTimeout(0)
-                .setHardTimeout(0)
-                .setXid(getNextTransactionId())
-                .build();
-
-        // match for router-mac and mpls packets
-        OFOxmEthType oxmpls = factory.oxms().ethType(EthType.MPLS_UNICAST);
-        /* TODO: need to read network config and need to allow only
-         the packets with DMAC as the correspondent router MAC address
-        OFOxmList oxmListMpls = OFOxmList.of(dmac, oxmpls);
-        OFMatchV3 matchMpls = factory.buildMatchV3()
-                .setOxmList(oxmListMpls).build();
-        */
-        OFOxmList oxmListMpls = OFOxmList.EMPTY;
-        OFMatchV3 matchMpls = factory.buildMatchV3()
-                .setOxmList(oxmList)
-                .build();
-
-        OFInstruction gotoTblMpls = factory.instructions().buildGotoTable()
-                .setTableId(TableId.of(mplsTableId)).build();
-        List<OFInstruction> instructionsMpls = Collections.singletonList(gotoTblMpls);
-        OFMessage mplsEntry = factory.buildFlowAdd()
-                .setTableId(TableId.of(tmacTableId))
-                .setMatch(matchMpls)
-                .setInstructions(instructionsMpls)
-                .setPriority(1001) // strict priority required lower than
-                        // multicastMac
-                .setBufferId(OFBufferId.NO_BUFFER)
-                .setIdleTimeout(0)
-                .setHardTimeout(0)
-                .setXid(getNextTransactionId())
-                .build();
-
-        log.debug("Adding termination-mac-rules in sw {}", getStringId());
-        List<OFMessage> msglist = new ArrayList<OFMessage>(2);
-        msglist.add(ipEntry);
-        msglist.add(mplsEntry);
-        write(msglist);
-    }
-
-    private MacAddress getRouterMacAddr() {
-        // TODO: need to read network config : RouterIp
-        return MacAddress.of("00:00:00:00:00:00");
+    @Override
+    public TableType getTableType(TableId tid) {
+        switch (tid.getValue()) {
+            case TABLE_IPV4_UNICAST:
+                return TableType.IP;
+            case TABLE_MPLS:
+                return TableType.MPLS;
+            case TABLE_ACL:
+                return TableType.ACL;
+            case TABLE_VLAN:
+                return TableType.VLAN;
+            case TABLE_TMAC:
+                return TableType.ETHER;
+            default:
+                log.error("Table type for Table id {} is not supported in the driver", tid);
+                return TableType.NONE;
+        }
     }
 
     private TableId getTableId(TableType tableType) {
@@ -556,6 +180,10 @@
                 return TableId.of(mplsTableId);
             case ACL:
                 return TableId.of(aclTableId);
+            case VLAN:
+                return TableId.of(vlanTableId);
+            case ETHER:
+                return TableId.of(tmacTableId);
             default: {
                 log.error("Table type {} is not supported in the driver", tableType);
                 return TableId.NONE;
@@ -563,19 +191,4 @@
         }
     }
 
-    private void sendHandshakeBarrier() throws IOException {
-        long xid = getNextTransactionId();
-        barrierXidToWaitFor = xid;
-        OFBarrierRequest br = factory()
-                .buildBarrierRequest()
-                .setXid(xid)
-                .build();
-        write(br);
-    }
-
-    @Override
-    public TableType getTableType(TableId tid) {
-        return TableType.NONE; // XXX this needs to be fixed
-    }
-
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/MPLS.java b/utils/misc/src/main/java/org/onlab/packet/MPLS.java
new file mode 100644
index 0000000..48a9121
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/MPLS.java
@@ -0,0 +1,115 @@
+package org.onlab.packet;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MPLS extends BasePacket {
+    public static final int ADDRESS_LENGTH = 4;
+    public static final byte PROTOCOL_IPV4 = 0x1;
+    public static final byte PROTOCOL_MPLS = 0x6;
+    public static final Map<Byte, Class<? extends IPacket>> PROTOCOL_CLASS_MAP;
+
+    static {
+        PROTOCOL_CLASS_MAP = new HashMap<Byte, Class<? extends IPacket>>();
+        PROTOCOL_CLASS_MAP.put(PROTOCOL_IPV4, IPv4.class);
+        PROTOCOL_CLASS_MAP.put(PROTOCOL_MPLS, MPLS.class);
+    }
+
+    protected int label; //20bits
+    protected byte bos; //1bit
+    protected byte ttl; //8bits
+    protected byte protocol;
+
+    /**
+     * Default constructor that sets the version to 4.
+     */
+    public MPLS() {
+        super();
+        this.bos = 1;
+        this.protocol = PROTOCOL_IPV4;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        if (payload != null) {
+            payload.setParent(this);
+            payloadData = payload.serialize();
+        }
+
+        byte[] data = new byte[(4 + ((payloadData != null) ? payloadData.length : 0)) ];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+
+        bb.putInt(((this.label & 0x000fffff) << 12) | ((this.bos & 0x1) << 8 | (this.ttl & 0xff)));
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+
+        return data;
+    }
+
+    @Override
+    public IPacket deserialize(byte[] data, int offset, int length) {
+        ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+        int mplsheader = bb.getInt();
+        this.label = ((mplsheader & 0xfffff000) >> 12);
+        this.bos = (byte) ((mplsheader & 0x00000100) >> 8);
+        this.bos = (byte) (mplsheader & 0x000000ff);
+        this.protocol = (this.bos == 1) ? PROTOCOL_IPV4 : PROTOCOL_MPLS;
+
+        IPacket payload;
+        if (IPv4.PROTOCOL_CLASS_MAP.containsKey(this.protocol)) {
+            Class<? extends IPacket> clazz = IPv4.PROTOCOL_CLASS_MAP.get(this.protocol);
+            try {
+                payload = clazz.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException("Error parsing payload for MPLS packet", e);
+            }
+        } else {
+            payload = new Data();
+        }
+        this.payload = payload.deserialize(data, bb.position(), bb.limit() - bb.position());
+        this.payload.setParent(this);
+
+        return this;
+    }
+
+    /**
+     * Returns the MPLS label.
+     *
+     * @return MPLS label
+     */
+    public int getLabel() {
+        return label;
+    }
+
+    /**
+     * Sets the MPLS label.
+     *
+     * @param label
+     */
+    public void setLabel(int label) {
+        this.label = label;
+    }
+
+    /**
+     * Returns the MPLS TTL of the packet.
+     *
+     * @return MPLS TTL of the packet
+     */
+    public byte getTtl() {
+        return ttl;
+    }
+
+    /**
+     * Sets the MPLS TTL of the packet.
+     *
+     * @param ttl MPLS TTL
+     */
+    public void setTtl(byte ttl) {
+        this.ttl = ttl;
+    }
+
+}