CORD-512 Support vSG <-> vRouter default route

- Support multiple subnets per port. getIpPort() will only return the first non-/32 and non-/0 subnet
    /32 is used as vSG subnet
    /0 is used as default gateway
- Support multiple L3 unicast group on a single port
    Change the way to generate the group ID and group key
- Special case for 0.0.0.0 host. Push a /0 to IP table instead of /32
- Implement vRouterConfig
    Put VR MAC to TMAC table of all leaves when config added
        When processEthDst see PortNumber.ANY in key, match ETH_DST only
- For OFDPA, wipe existing instruction before sending to controller
    So packet that misses L3 unicast table won't be sent to controller twice
- For SpringOpenTTP, pop VLAN before sending to controller
- Move several constant definitions to SegmentRoutingService
- Add minimum priority for IP rules such that /0 won't collide with zero priority default rules
- Update the config sample
    Use VLAN=-1 for hosts
    Add example for default route

Change-Id: Id751697ce36a7e5c13b3859350ff21b585c38525
diff --git a/apps/segmentrouting/pom.xml b/apps/segmentrouting/pom.xml
index 5562b9f..68777ef 100644
--- a/apps/segmentrouting/pom.xml
+++ b/apps/segmentrouting/pom.xml
@@ -97,6 +97,14 @@
             <artifactId>onlab-junit</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+            <classifier>tests</classifier>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index a8d16e8..d59fb12 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -168,7 +168,7 @@
                     treatment, ByteBuffer.wrap(payload.serialize()));
             srManager.packetService.emit(packet);
         } else {
-            log.warn("Send a MPLS packet as a ICMP response");
+            log.info("Send a MPLS packet as a ICMP response");
             TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                     .setOutput(outport.port())
                     .build();
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigEventHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigEventHandler.java
new file mode 100644
index 0000000..afb7ec9
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigEventHandler.java
@@ -0,0 +1,156 @@
+package org.onosproject.segmentrouting;
+
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flowobjective.DefaultFilteringObjective;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
+import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Handles network config events.
+ */
+public class NetworkConfigEventHandler {
+    private static final Logger log = LoggerFactory.getLogger(NetworkConfigEventHandler.class);
+    private final SegmentRoutingManager srManager;
+    private final DeviceService deviceService;
+
+    /**
+     * Constructs Network Config Event Handler.
+     *
+     * @param srManager instance of {@link SegmentRoutingManager}
+     */
+    public NetworkConfigEventHandler(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+        this.deviceService = srManager.deviceService;
+    }
+
+    /**
+     * Processes vRouter config added event.
+     *
+     * @param event network config added event
+     */
+    protected void processVRouterConfigAdded(NetworkConfigEvent event) {
+        log.info("Processing vRouter CONFIG_ADDED");
+        SegmentRoutingAppConfig config = (SegmentRoutingAppConfig) event.config().get();
+        deviceService.getAvailableDevices().forEach(device -> {
+            populateVRouter(device.id(), getMacAddresses(config));
+        });
+    }
+
+    /**
+     * Processes vRouter config updated event.
+     *
+     * @param event network config updated event
+     */
+    protected void processVRouterConfigUpdated(NetworkConfigEvent event) {
+        log.info("Processing vRouter CONFIG_UPDATED");
+        SegmentRoutingAppConfig config = (SegmentRoutingAppConfig) event.config().get();
+        SegmentRoutingAppConfig prevConfig = (SegmentRoutingAppConfig) event.prevConfig().get();
+        deviceService.getAvailableDevices().forEach(device -> {
+            Set<MacAddress> macAddresses = getMacAddresses(config);
+            Set<MacAddress> prevMacAddresses = getMacAddresses(prevConfig);
+            // Avoid removing and re-adding unchanged MAC addresses since
+            // FlowObjective does not guarantee the execution order.
+            Set<MacAddress> sameMacAddresses = new HashSet<>(macAddresses);
+            sameMacAddresses.retainAll(prevMacAddresses);
+            macAddresses.removeAll(sameMacAddresses);
+            prevMacAddresses.removeAll(sameMacAddresses);
+
+            revokeVRouter(device.id(), prevMacAddresses);
+            populateVRouter(device.id(), macAddresses);
+        });
+
+    }
+
+    /**
+     * Processes vRouter config removed event.
+     *
+     * @param event network config removed event
+     */
+    protected void processVRouterConfigRemoved(NetworkConfigEvent event) {
+        log.info("Processing vRouter CONFIG_REMOVED");
+        SegmentRoutingAppConfig prevConfig = (SegmentRoutingAppConfig) event.prevConfig().get();
+        deviceService.getAvailableDevices().forEach(device -> {
+            revokeVRouter(device.id(), getMacAddresses(prevConfig));
+        });
+    }
+
+    /**
+     * Populates initial vRouter rules.
+     *
+     * @param deviceId device ID
+     */
+    public void initVRouters(DeviceId deviceId) {
+        SegmentRoutingAppConfig config =
+                srManager.cfgService.getConfig(srManager.appId, SegmentRoutingAppConfig.class);
+        populateVRouter(deviceId, getMacAddresses(config));
+    }
+
+    private void populateVRouter(DeviceId deviceId, Set<MacAddress> pendingAdd) {
+        if (!isEdge(deviceId)) {
+            return;
+        }
+        getVRouterFlowObjBuilders(pendingAdd).forEach(foBuilder -> {
+            srManager.flowObjectiveService.
+                    filter(deviceId, foBuilder.add(new SRObjectiveContext(deviceId,
+                            SRObjectiveContext.ObjectiveType.FILTER)));
+        });
+    }
+
+    private void revokeVRouter(DeviceId deviceId, Set<MacAddress> pendingRemove) {
+        if (!isEdge(deviceId)) {
+            return;
+        }
+        getVRouterFlowObjBuilders(pendingRemove).forEach(foBuilder -> {
+            srManager.flowObjectiveService.
+                    filter(deviceId, foBuilder.remove(new SRObjectiveContext(deviceId,
+                            SRObjectiveContext.ObjectiveType.FILTER)));
+        });
+    }
+
+    private Set<FilteringObjective.Builder> getVRouterFlowObjBuilders(Set<MacAddress> macAddresses) {
+        ImmutableSet.Builder<FilteringObjective.Builder> setBuilder = ImmutableSet.builder();
+        macAddresses.forEach(macAddress -> {
+            FilteringObjective.Builder fobuilder = DefaultFilteringObjective.builder();
+            fobuilder.withKey(Criteria.matchInPort(PortNumber.ANY))
+                    .addCondition(Criteria.matchEthDst(macAddress))
+                    .permit()
+                    .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
+                    .fromApp(srManager.appId);
+            setBuilder.add(fobuilder);
+        });
+        return setBuilder.build();
+    }
+
+    private Set<MacAddress> getMacAddresses(SegmentRoutingAppConfig config) {
+        if (config == null) {
+            return ImmutableSet.of();
+        }
+
+        HashSet<MacAddress> macAddresses = new HashSet<>();
+        config.vRouterMacs().forEach(mac -> {
+            macAddresses.add(mac);
+        });
+        return ImmutableSet.copyOf(macAddresses);
+    }
+
+    private boolean isEdge(DeviceId deviceId) {
+        try {
+            if (srManager.deviceConfiguration.isEdgeDevice(deviceId)) {
+                return true;
+            }
+        } catch (DeviceConfigNotFoundException e) { }
+        return false;
+    }
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 6017e8b..10ac6d6 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -39,11 +39,8 @@
 import org.onosproject.net.flowobjective.DefaultForwardingObjective;
 import org.onosproject.net.flowobjective.FilteringObjective;
 import org.onosproject.net.flowobjective.ForwardingObjective;
-import org.onosproject.net.flowobjective.Objective;
-import org.onosproject.net.flowobjective.ObjectiveError;
 import org.onosproject.net.flowobjective.ForwardingObjective.Builder;
 import org.onosproject.net.flowobjective.ForwardingObjective.Flag;
-import org.onosproject.net.flowobjective.ObjectiveContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -67,14 +64,6 @@
     private SegmentRoutingManager srManager;
     private DeviceConfiguration config;
 
-    private static final int HIGHEST_PRIORITY = 0xffff;
-    //
-    private static final int XCONNECT_PRIORITY = 1000;
-    private static final int DEFAULT_PRIORITY = 100;
-    private static final int FLOOD_PRIORITY = 5;
-    private static final long OFPP_MAX = 0xffffff00L;
-
-
     /**
      * Creates a RoutingRulePopulator object.
      *
@@ -160,12 +149,21 @@
             throws DeviceConfigNotFoundException {
         MacAddress deviceMac;
         deviceMac = config.getDeviceMac(deviceId);
+        int priority;
 
         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
 
         sbuilder.matchEthType(Ethernet.TYPE_IPV4);
-        sbuilder.matchIPDst(IpPrefix.valueOf(hostIp, IpPrefix.MAX_INET_MASK_LENGTH));
+        // Special case for default route
+        if (hostIp.isZero()) {
+            sbuilder.matchIPDst(IpPrefix.valueOf(hostIp, 0));
+            priority = SegmentRoutingService.MIN_IP_PRIORITY;
+        } else {
+            Ip4Prefix hostIpPrefix = Ip4Prefix.valueOf(hostIp, IpPrefix.MAX_INET_MASK_LENGTH);
+            sbuilder.matchIPDst(hostIpPrefix);
+            priority = getPriorityFromPrefix(hostIpPrefix);
+        }
         TrafficSelector selector = sbuilder.build();
 
         tbuilder.deferred()
@@ -192,7 +190,8 @@
                 .withSelector(selector)
                 .nextStep(portNextObjId)
                 .fromApp(srManager.appId).makePermanent()
-                .withPriority(DEFAULT_PRIORITY).withFlag(ForwardingObjective.Flag.SPECIFIC);
+                .withPriority(priority)
+                .withFlag(ForwardingObjective.Flag.SPECIFIC);
     }
 
     /**
@@ -277,7 +276,7 @@
                 .makePermanent()
                 .nextStep(nextId)
                 .withSelector(selector)
-                .withPriority(2000 * ipPrefix.prefixLength())
+                .withPriority(getPriorityFromPrefix(ipPrefix))
                 .withFlag(ForwardingObjective.Flag.SPECIFIC);
         if (treatment != null) {
             fwdBuilder.withTreatment(treatment);
@@ -386,7 +385,7 @@
         for (ForwardingObjective.Builder fwdObjBuilder : fwdObjBuilders) {
             ((Builder) ((Builder) fwdObjBuilder.fromApp(srManager.appId)
                     .makePermanent()).withSelector(selector)
-                    .withPriority(DEFAULT_PRIORITY))
+                    .withPriority(SegmentRoutingService.DEFAULT_PRIORITY))
                     .withFlag(ForwardingObjective.Flag.SPECIFIC);
             srManager.flowObjectiveService.
                 forward(deviceId,
@@ -472,7 +471,9 @@
         }
 
         for (Port port : srManager.deviceService.getPorts(deviceId)) {
-            if (port.number().toLong() > 0 && port.number().toLong() < OFPP_MAX) {
+            if (port.number().toLong() > 0 &&
+                    port.number().toLong() < SegmentRoutingService.OFPP_MAX &&
+                    port.isEnabled()) {
                 Ip4Prefix portSubnet = config.getPortSubnet(deviceId, port.number());
                 VlanId assignedVlan = (portSubnet == null)
                         ? VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET)
@@ -482,7 +483,7 @@
                 fob.withKey(Criteria.matchInPort(port.number()))
                 .addCondition(Criteria.matchEthDst(deviceMac))
                 .addCondition(Criteria.matchVlanId(VlanId.NONE))
-                .withPriority(DEFAULT_PRIORITY);
+                .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
                 // vlan assignment is valid only if this instance is master
                 if (srManager.mastershipService.isLocalMaster(deviceId)) {
                     TrafficTreatment tt = DefaultTrafficTreatment.builder()
@@ -520,7 +521,7 @@
             return;
         }
         ForwardingObjective.Builder puntIp = DefaultForwardingObjective.builder();
-        Set<Ip4Address> allIps = new HashSet<Ip4Address>(config.getPortIPs(deviceId));
+        Set<Ip4Address> allIps = new HashSet<>(config.getPortIPs(deviceId));
         allIps.add(routerIp);
         for (Ip4Address ipaddr : allIps) {
             TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
@@ -532,7 +533,7 @@
             puntIp.withSelector(sbuilder.build());
             puntIp.withTreatment(tbuilder.build());
             puntIp.withFlag(Flag.VERSATILE)
-                .withPriority(HIGHEST_PRIORITY)
+                .withPriority(SegmentRoutingService.HIGHEST_PRIORITY)
                 .makePermanent()
                 .fromApp(srManager.appId);
             log.debug("Installing forwarding objective to punt port IP addresses");
@@ -576,7 +577,7 @@
             fob.withFlag(Flag.SPECIFIC)
                     .withSelector(sbuilder.build())
                     .nextStep(nextId)
-                    .withPriority(FLOOD_PRIORITY)
+                    .withPriority(SegmentRoutingService.FLOOD_PRIORITY)
                     .fromApp(srManager.appId)
                     .makePermanent();
 
@@ -611,7 +612,7 @@
                 fob.withKey(Criteria.matchInPort(connectPoint.port()))
                         .addCondition(Criteria.matchVlanId(vlanId))
                         .addCondition(Criteria.matchEthDst(MacAddress.NONE))
-                        .withPriority(XCONNECT_PRIORITY);
+                        .withPriority(SegmentRoutingService.XCONNECT_PRIORITY);
 
                 fob.permit().fromApp(srManager.appId);
                 srManager.flowObjectiveService
@@ -657,7 +658,7 @@
             fob.withFlag(Flag.SPECIFIC)
                     .withSelector(sbuilder.build())
                     .nextStep(nextId)
-                    .withPriority(DEFAULT_PRIORITY)
+                    .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
                     .fromApp(srManager.appId)
                     .makePermanent();
 
@@ -671,29 +672,9 @@
         });
     }
 
-    private static class SRObjectiveContext implements ObjectiveContext {
-        enum ObjectiveType {
-            FILTER,
-            FORWARDING
-        }
-        final DeviceId deviceId;
-        final ObjectiveType type;
-
-        SRObjectiveContext(DeviceId deviceId, ObjectiveType type) {
-            this.deviceId = deviceId;
-            this.type = type;
-        }
-        @Override
-        public void onSuccess(Objective objective) {
-            log.debug("{} objective operation successful in device {}",
-                      type.name(), deviceId);
-        }
-
-        @Override
-        public void onError(Objective objective, ObjectiveError error) {
-            log.warn("{} objective {} operation failed with error: {} in device {}",
-                     type.name(), objective, error, deviceId);
-        }
+    private int getPriorityFromPrefix(IpPrefix prefix) {
+        return (prefix.isIp4()) ?
+                2000 * prefix.prefixLength() + SegmentRoutingService.MIN_IP_PRIORITY :
+                500 * prefix.prefixLength() + SegmentRoutingService.MIN_IP_PRIORITY;
     }
-
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SRObjectiveContext.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SRObjectiveContext.java
new file mode 100644
index 0000000..8dd75de
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SRObjectiveContext.java
@@ -0,0 +1,40 @@
+package org.onosproject.segmentrouting;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Segment Routing Flow Objective Context.
+ */
+public class SRObjectiveContext implements ObjectiveContext {
+    enum ObjectiveType {
+        FILTER,
+        FORWARDING
+    }
+    private final DeviceId deviceId;
+    private final ObjectiveType type;
+
+    private static final Logger log = LoggerFactory
+            .getLogger(SegmentRoutingManager.class);
+
+    SRObjectiveContext(DeviceId deviceId, ObjectiveType type) {
+        this.deviceId = deviceId;
+        this.type = type;
+    }
+    @Override
+    public void onSuccess(Objective objective) {
+        log.debug("{} objective operation successful in device {}",
+                type.name(), deviceId);
+    }
+
+    @Override
+    public void onError(Objective objective, ObjectiveError error) {
+        log.warn("{} objective {} operation failed with error: {} in device {}",
+                type.name(), objective, error, deviceId);
+    }
+}
+
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 592c0b1..32e03de 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -54,7 +54,8 @@
 import org.onosproject.net.packet.PacketPriority;
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
-import org.onosproject.segmentrouting.config.SegmentRoutingConfig;
+import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
+import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
 import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
 import org.onosproject.segmentrouting.grouphandler.NeighborSet;
 import org.onosproject.segmentrouting.grouphandler.NeighborSetNextObjectiveStoreKey;
@@ -130,6 +131,12 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected MastershipService mastershipService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry cfgService;
+
     protected ArpHandler arpHandler = null;
     protected IcmpHandler icmpHandler = null;
     protected IpHandler ipHandler = null;
@@ -143,7 +150,9 @@
     private InternalPacketProcessor processor = null;
     private InternalLinkListener linkListener = null;
     private InternalDeviceListener deviceListener = null;
+    private NetworkConfigEventHandler netcfgHandler = null;
     private InternalEventHandler eventHandler = new InternalEventHandler();
+    private final InternalHostListener hostListener = new InternalHostListener();
 
     private ScheduledExecutorService executorService = Executors
             .newScheduledThreadPool(1);
@@ -181,27 +190,28 @@
     private EventuallyConsistentMap<String, Tunnel> tunnelStore = null;
     private EventuallyConsistentMap<String, Policy> policyStore = null;
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected StorageService storageService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected NetworkConfigRegistry cfgService;
-
     private final InternalConfigListener cfgListener =
             new InternalConfigListener(this);
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    private final ConfigFactory cfgFactory =
-            new ConfigFactory(SubjectFactories.DEVICE_SUBJECT_FACTORY,
-                              SegmentRoutingConfig.class,
+    private final ConfigFactory<DeviceId, SegmentRoutingDeviceConfig> cfgDeviceFactory =
+            new ConfigFactory<DeviceId, SegmentRoutingDeviceConfig>(SubjectFactories.DEVICE_SUBJECT_FACTORY,
+                              SegmentRoutingDeviceConfig.class,
                               "segmentrouting") {
                 @Override
-                public SegmentRoutingConfig createConfig() {
-                    return new SegmentRoutingConfig();
+                public SegmentRoutingDeviceConfig createConfig() {
+                    return new SegmentRoutingDeviceConfig();
                 }
             };
 
-    private final InternalHostListener hostListener = new InternalHostListener();
+    private final ConfigFactory<ApplicationId, SegmentRoutingAppConfig> cfgAppFactory =
+            new ConfigFactory<ApplicationId, SegmentRoutingAppConfig>(SubjectFactories.APP_SUBJECT_FACTORY,
+                    SegmentRoutingAppConfig.class,
+                    "segmentrouting") {
+                @Override
+                public SegmentRoutingAppConfig createConfig() {
+                    return new SegmentRoutingAppConfig();
+                }
+            };
 
     private Object threadSchedulerLock = new Object();
     private static int numOfEventsQueued = 0;
@@ -223,7 +233,7 @@
     @Activate
     protected void activate() {
         appId = coreService
-                .registerApplication("org.onosproject.segmentrouting");
+                .registerApplication(SR_APP_ID);
 
         kryoBuilder = new KryoNamespace.Builder()
             .register(NeighborSetNextObjectiveStoreKey.class,
@@ -309,14 +319,15 @@
                 .build();
 
         cfgService.addListener(cfgListener);
-        cfgService.registerConfigFactory(cfgFactory);
-
-        hostService.addListener(hostListener);
+        cfgService.registerConfigFactory(cfgDeviceFactory);
+        cfgService.registerConfigFactory(cfgAppFactory);
 
         processor = new InternalPacketProcessor();
         linkListener = new InternalLinkListener();
         deviceListener = new InternalDeviceListener();
+        netcfgHandler = new NetworkConfigEventHandler(this);
 
+        hostService.addListener(hostListener);
         packetService.addProcessor(processor, PacketProcessor.director(2));
         linkService.addListener(linkListener);
         deviceService.addListener(deviceListener);
@@ -334,7 +345,8 @@
     @Deactivate
     protected void deactivate() {
         cfgService.removeListener(cfgListener);
-        cfgService.unregisterConfigFactory(cfgFactory);
+        cfgService.unregisterConfigFactory(cfgDeviceFactory);
+        cfgService.unregisterConfigFactory(cfgAppFactory);
 
         // Withdraw ARP packet-in
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
@@ -456,10 +468,17 @@
             nextAssignedVlan = (short) (Collections.min(assignedVlans) - 1);
         }
         for (Ip4Prefix unsub : unassignedSubnets) {
-            subnetVidStore.put(new SubnetAssignedVidStoreKey(deviceId, unsub),
-                               VlanId.vlanId(nextAssignedVlan--));
-            log.info("Assigned vlan: {} to subnet: {} on device: {}",
-                      nextAssignedVlan + 1, unsub, deviceId);
+            // Special case for default route. Assign default VLAN ID to /32 and /0 subnets
+            if (unsub.prefixLength() == IpPrefix.MAX_INET_MASK_LENGTH ||
+                    unsub.prefixLength() == 0) {
+                subnetVidStore.put(new SubnetAssignedVidStoreKey(deviceId, unsub),
+                        VlanId.vlanId(ASSIGNED_VLAN_NO_SUBNET));
+            } else {
+                subnetVidStore.put(new SubnetAssignedVidStoreKey(deviceId, unsub),
+                        VlanId.vlanId(nextAssignedVlan--));
+                log.info("Assigned vlan: {} to subnet: {} on device: {}",
+                        nextAssignedVlan + 1, unsub, deviceId);
+            }
         }
 
         return subnetVidStore.get(new SubnetAssignedVidStoreKey(deviceId, subnet));
@@ -766,6 +785,8 @@
             groupHandler.createGroupsForXConnect(device.id());
             routingRulePopulator.populateXConnectBroadcastRule(device.id());
         }
+
+        netcfgHandler.initVRouters(device.id());
     }
 
     private void processPortRemoved(Device device, Port port) {
@@ -851,14 +872,33 @@
 
         @Override
         public void event(NetworkConfigEvent event) {
-            if (event.configClass().equals(SegmentRoutingConfig.class)) {
-                if (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED) {
-                    log.info("Network configuration added.");
-                    configureNetwork();
+            // TODO move this part to NetworkConfigEventHandler
+            if (event.configClass().equals(SegmentRoutingDeviceConfig.class)) {
+                switch (event.type()) {
+                    case CONFIG_ADDED:
+                        log.info("Segment Routing Config added.");
+                        configureNetwork();
+                        break;
+                    case CONFIG_UPDATED:
+                        log.info("Segment Routing Config updated.");
+                        // TODO support dynamic configuration
+                        break;
+                    default:
+                        break;
                 }
-                if (event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) {
-                    log.info("Network configuration updated.");
-                    // TODO support dynamic configuration
+            } else if (event.configClass().equals(SegmentRoutingAppConfig.class)) {
+                switch (event.type()) {
+                    case CONFIG_ADDED:
+                        netcfgHandler.processVRouterConfigAdded(event);
+                        break;
+                    case CONFIG_UPDATED:
+                        netcfgHandler.processVRouterConfigUpdated(event);
+                        break;
+                    case CONFIG_REMOVED:
+                        netcfgHandler.processVRouterConfigRemoved(event);
+                        break;
+                    default:
+                        break;
                 }
             }
         }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
index 44bd453..f82ad3e 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
@@ -21,6 +21,40 @@
  * Segment Routing Service for REST API.
  */
 public interface SegmentRoutingService {
+    /**
+     * Segment Routing App ID.
+     */
+    String SR_APP_ID = "org.onosproject.segmentrouting";
+
+    /**
+     * Highest flow priority.
+     */
+    int HIGHEST_PRIORITY = 0xffff;
+
+    /**
+     * VLAN cross-connect priority.
+     */
+    int XCONNECT_PRIORITY = 1000;
+
+    /**
+     * Default flow priority.
+     */
+    int DEFAULT_PRIORITY = 100;
+
+    /**
+     * Minimum IP priority.
+     *
+     * Should > 0 such that priority of /0 will not conflict with lowest
+     * priority default entries.
+     */
+    int MIN_IP_PRIORITY = 10;
+
+    /**
+     * Subnet flooding flow priority.
+     */
+    int FLOOD_PRIORITY = 5;
+
+    long OFPP_MAX = 0xffffff00L;
 
     /**
      * Returns all tunnels.
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index 3d2d337..db4bc63 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
@@ -15,9 +15,12 @@
  */
 package org.onosproject.segmentrouting.config;
 
+import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.SetMultimap;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onosproject.incubator.net.config.basics.ConfigException;
@@ -37,6 +40,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -59,13 +63,13 @@
         Ip4Address ip;
         MacAddress mac;
         boolean isEdge;
-        HashMap<PortNumber, Ip4Address> gatewayIps;
-        HashMap<PortNumber, Ip4Prefix> subnets;
+        Map<PortNumber, Ip4Address> gatewayIps;
+        SetMultimap<PortNumber, Ip4Prefix> subnets;
         Map<Integer, Set<Integer>> adjacencySids;
 
         public SegmentRouterInfo() {
             gatewayIps = new HashMap<>();
-            subnets = new HashMap<>();
+            subnets = HashMultimap.create();
         }
     }
 
@@ -78,10 +82,10 @@
     public DeviceConfiguration(NetworkConfigRegistry cfgService) {
         // Read config from device subject, excluding gatewayIps and subnets.
         Set<DeviceId> deviceSubjects =
-                cfgService.getSubjects(DeviceId.class, SegmentRoutingConfig.class);
+                cfgService.getSubjects(DeviceId.class, SegmentRoutingDeviceConfig.class);
         deviceSubjects.forEach(subject -> {
-            SegmentRoutingConfig config =
-                cfgService.getConfig(subject, SegmentRoutingConfig.class);
+            SegmentRoutingDeviceConfig config =
+                cfgService.getConfig(subject, SegmentRoutingDeviceConfig.class);
             SegmentRouterInfo info = new SegmentRouterInfo();
             info.deviceId = subject;
             info.nodeSid = config.nodeSid();
@@ -119,7 +123,11 @@
                     // Extract subnet information
                     Set<InterfaceIpAddress> interfaceAddresses = networkInterface.ipAddresses();
                     interfaceAddresses.forEach(interfaceAddress -> {
-                        info.gatewayIps.put(port, interfaceAddress.ipAddress().getIp4Address());
+                        // Do not add /0 and /32 to gateway IP list
+                        int prefixLength = interfaceAddress.subnetAddress().prefixLength();
+                        if (prefixLength != 0 && prefixLength != IpPrefix.MAX_INET_MASK_LENGTH) {
+                            info.gatewayIps.put(port, interfaceAddress.ipAddress().getIp4Address());
+                        }
                         info.subnets.put(port, interfaceAddress.subnetAddress().getIp4Prefix());
                     });
 
@@ -247,9 +255,13 @@
         Map<Ip4Prefix, List<PortNumber>> subnetPortMap = new HashMap<>();
 
         // Construct subnet-port mapping from port-subnet mapping
-        Map<PortNumber, Ip4Prefix> portSubnetMap =
+        SetMultimap<PortNumber, Ip4Prefix> portSubnetMap =
                 this.deviceConfigMap.get(deviceId).subnets;
-        portSubnetMap.forEach((port, subnet) -> {
+
+        portSubnetMap.entries().forEach(entry -> {
+            PortNumber port = entry.getKey();
+            Ip4Prefix subnet = entry.getValue();
+
             if (subnetPortMap.containsKey(subnet)) {
                 subnetPortMap.get(subnet).add(port);
             } else {
@@ -258,7 +270,6 @@
                 subnetPortMap.put(subnet, ports);
             }
         });
-
         return subnetPortMap;
     }
 
@@ -322,21 +333,6 @@
     }
 
     /**
-     * Returns the configured IP addresses per port
-     * for a segment router.
-     *
-     * @param deviceId device identifier
-     * @return map of port to gateway IP addresses or null if not found
-     */
-    public Map<PortNumber, Ip4Address> getPortIPMap(DeviceId deviceId) {
-        SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
-        if (srinfo != null) {
-            return srinfo.gatewayIps;
-        }
-        return null;
-    }
-
-    /**
      * Returns the configured subnet prefixes for a segment router.
      *
      * @param deviceId device identifier
@@ -353,8 +349,8 @@
     }
 
     /**
-     *  Returns the configured subnet on the given port, or null if no
-     *  subnet has been configured on the port.
+     *  Returns the configured non-/32 and non-/0 subnet on the given port,
+     *  or null if no subnet has been configured on the port.
      *
      *  @param deviceId device identifier
      *  @param pnum  port identifier
@@ -363,7 +359,12 @@
     public Ip4Prefix getPortSubnet(DeviceId deviceId, PortNumber pnum) {
         SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
         if (srinfo != null) {
-            return srinfo.subnets.get(pnum);
+            Optional<Ip4Prefix> result = srinfo.subnets.get(pnum).stream()
+                    .filter(subnet ->
+                            subnet.getIp4Prefix().prefixLength() != IpPrefix.MAX_INET_MASK_LENGTH &&
+                            subnet.getIp4Prefix().prefixLength() != 0)
+                    .findFirst();
+            return (result.isPresent()) ? result.get() : null;
         }
         return null;
     }
@@ -378,7 +379,7 @@
     public Ip4Address getRouterIpAddressForASubnetHost(Ip4Address destIpAddress) {
         for (Map.Entry<DeviceId, SegmentRouterInfo> entry:
                     deviceConfigMap.entrySet()) {
-            for (Ip4Prefix prefix:entry.getValue().subnets.values()) {
+            for (Ip4Prefix prefix : entry.getValue().subnets.values()) {
                 if (prefix.contains(destIpAddress)) {
                     return entry.getValue().ip;
                 }
@@ -428,7 +429,8 @@
         }
 
         for (Ip4Prefix subnet: subnets) {
-            if (subnet.contains(hostIp)) {
+            // Exclude /0 since it is a special case used for default route
+            if (subnet.prefixLength() != 0 && subnet.contains(hostIp)) {
                 return true;
             }
         }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfig.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfig.java
new file mode 100644
index 0000000..e39fb18
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfig.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016 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.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * App configuration object for Segment Routing.
+ */
+public class SegmentRoutingAppConfig extends Config<ApplicationId> {
+    private static final String VROUTER_MACS = "vRouterMacs";
+
+    @Override
+    public boolean isValid() {
+        return hasOnlyFields(VROUTER_MACS) && vRouterMacs() != null;
+    }
+
+    /**
+     * Gets vRouters from the config.
+     *
+     * @return a set of vRouter MAC addresses
+     */
+    public Set<MacAddress> vRouterMacs() {
+        if (!object.has(VROUTER_MACS)) {
+            return null;
+        }
+
+        ImmutableSet.Builder<MacAddress> builder = ImmutableSet.builder();
+        ArrayNode arrayNode = (ArrayNode) object.path(VROUTER_MACS);
+        for (JsonNode jsonNode : arrayNode) {
+            MacAddress mac;
+
+            String macStr = jsonNode.asText(null);
+            if (macStr == null) {
+                return null;
+            }
+            try {
+                mac = MacAddress.valueOf(macStr);
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+
+            builder.add(mac);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Sets vRouters to the config.
+     *
+     * @param vRouterMacs a set of vRouter MAC addresses
+     * @return this {@link SegmentRoutingAppConfig}
+     */
+    public SegmentRoutingAppConfig setVRouterMacs(Set<MacAddress> vRouterMacs) {
+        if (vRouterMacs == null) {
+            object.remove(VROUTER_MACS);
+        } else {
+            ArrayNode arrayNode = mapper.createArrayNode();
+
+            vRouterMacs.forEach(mac -> {
+                arrayNode.add(mac.toString());
+            });
+
+            object.set(VROUTER_MACS, arrayNode);
+        }
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("vRouterMacs", vRouterMacs())
+                .toString();
+    }
+}
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingConfig.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
similarity index 87%
rename from apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingConfig.java
rename to apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
index 64486c6..10c69ca 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingConfig.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
@@ -34,7 +34,7 @@
 /**
  * Configuration object for Segment Routing Application.
  */
-public class SegmentRoutingConfig extends Config<DeviceId> {
+public class SegmentRoutingDeviceConfig extends Config<DeviceId> {
     private static final String NAME = "name";
     private static final String IP = "routerIp";
     private static final String MAC = "routerMac";
@@ -71,8 +71,8 @@
      * @param name name of the router.
      * @return the config of the router.
      */
-    public SegmentRoutingConfig setName(String name) {
-        return (SegmentRoutingConfig) setOrClear(NAME, name);
+    public SegmentRoutingDeviceConfig setName(String name) {
+        return (SegmentRoutingDeviceConfig) setOrClear(NAME, name);
     }
 
     /**
@@ -91,8 +91,8 @@
      * @param ip IP address of the router.
      * @return the config of the router.
      */
-    public SegmentRoutingConfig setRouterIp(String ip) {
-        return (SegmentRoutingConfig) setOrClear(IP, ip);
+    public SegmentRoutingDeviceConfig setRouterIp(String ip) {
+        return (SegmentRoutingDeviceConfig) setOrClear(IP, ip);
     }
 
     /**
@@ -111,8 +111,8 @@
      * @param mac MAC address of the router.
      * @return the config of the router.
      */
-    public SegmentRoutingConfig setRouterMac(String mac) {
-        return (SegmentRoutingConfig) setOrClear(MAC, mac);
+    public SegmentRoutingDeviceConfig setRouterMac(String mac) {
+        return (SegmentRoutingDeviceConfig) setOrClear(MAC, mac);
     }
 
     /**
@@ -130,8 +130,8 @@
      * @param sid node SID of the router.
      * @return the config of the router.
      */
-    public SegmentRoutingConfig setNodeSid(int sid) {
-        return (SegmentRoutingConfig) setOrClear(SID, sid);
+    public SegmentRoutingDeviceConfig setNodeSid(int sid) {
+        return (SegmentRoutingDeviceConfig) setOrClear(SID, sid);
     }
 
     /**
@@ -154,8 +154,8 @@
      * @param isEdgeRouter true if the router is an edge router.
      * @return the config of the router.
      */
-    public SegmentRoutingConfig setIsEdgeRouter(boolean isEdgeRouter) {
-        return (SegmentRoutingConfig) setOrClear(EDGE, isEdgeRouter);
+    public SegmentRoutingDeviceConfig setIsEdgeRouter(boolean isEdgeRouter) {
+        return (SegmentRoutingDeviceConfig) setOrClear(EDGE, isEdgeRouter);
     }
 
     /**
@@ -197,7 +197,7 @@
      * @param adjacencySids adjacency SIDs of the router.
      * @return the config of the router.
      */
-    public SegmentRoutingConfig setAdjacencySids(Map<Integer, Set<Integer>> adjacencySids) {
+    public SegmentRoutingDeviceConfig setAdjacencySids(Map<Integer, Set<Integer>> adjacencySids) {
         if (adjacencySids == null) {
             object.remove(ADJSIDS);
         } else {
diff --git a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfigTest.java b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfigTest.java
new file mode 100644
index 0000000..7755059
--- /dev/null
+++ b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfigTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2016 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.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
+import org.onosproject.segmentrouting.SegmentRoutingService;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for class {@link SegmentRoutingAppConfig}.
+ */
+public class SegmentRoutingAppConfigTest {
+    private static final ApplicationId APP_ID =
+            new TestApplicationId(SegmentRoutingService.SR_APP_ID);
+
+    private SegmentRoutingAppConfig config;
+    private MacAddress routerMac1;
+    private MacAddress routerMac2;
+    private MacAddress routerMac3;
+
+    /**
+     * Initialize test related variables.
+     *
+     * @throws Exception
+     */
+    @Before
+    public void setUp() throws Exception {
+        String jsonString = "{" +
+                "\"vRouterMacs\" : [" +
+                "    \"00:00:00:00:00:01\"," +
+                "    \"00:00:00:00:00:02\"" +
+                "]}";
+
+        routerMac1 = MacAddress.valueOf("00:00:00:00:00:01");
+        routerMac2 = MacAddress.valueOf("00:00:00:00:00:02");
+        routerMac3 = MacAddress.valueOf("00:00:00:00:00:03");
+
+        ApplicationId subject = APP_ID;
+        String key = SegmentRoutingService.SR_APP_ID;
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonNode = mapper.readTree(jsonString);
+        ConfigApplyDelegate delegate = new MockDelegate();
+
+        config = new SegmentRoutingAppConfig();
+        config.init(subject, key, jsonNode, mapper, delegate);
+    }
+
+    /**
+     * Tests vRouters getter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testVRouters() throws Exception {
+        assertTrue(config.isValid());
+
+        Set<MacAddress> vRouters = config.vRouterMacs();
+        assertThat(vRouters.size(), is(2));
+        assertTrue(vRouters.contains(routerMac1));
+        assertTrue(vRouters.contains(routerMac2));
+    }
+
+    /**
+     * Tests vRouters setter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetVRouters() throws Exception {
+        ImmutableSet.Builder<MacAddress> builder = ImmutableSet.builder();
+        builder.add(routerMac3);
+        config.setVRouterMacs(builder.build());
+
+        Set<MacAddress> macs = config.vRouterMacs();
+        assertThat(macs.size(), is(1));
+        assertTrue(macs.contains(routerMac3));
+    }
+
+    private class MockDelegate implements ConfigApplyDelegate {
+        @Override
+        public void onApply(Config config) {
+        }
+    }
+}
\ No newline at end of file
diff --git a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingConfigTest.java b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java
similarity index 94%
rename from apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingConfigTest.java
rename to apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java
index 3e5daa5..fd661b6 100644
--- a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingConfigTest.java
+++ b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java
@@ -36,10 +36,10 @@
 import static org.junit.Assert.assertTrue;
 
 /**
- * Tests for class {@link SegmentRoutingConfig}.
+ * Tests for class {@link SegmentRoutingDeviceConfig}.
  */
-public class SegmentRoutingConfigTest {
-    private SegmentRoutingConfig config;
+public class SegmentRoutingDeviceConfigTest {
+    private SegmentRoutingDeviceConfig config;
     private Map<Integer, Set<Integer>> adjacencySids1;
     private Map<Integer, Set<Integer>> adjacencySids2;
 
@@ -72,12 +72,12 @@
         adjacencySids2.put(300, ports3);
 
         DeviceId subject = DeviceId.deviceId("of:0000000000000001");
-        String key = "org.onosproject.segmentrouting";
+        String key = "segmentrouting";
         ObjectMapper mapper = new ObjectMapper();
         JsonNode jsonNode = mapper.readTree(jsonString);
         ConfigApplyDelegate delegate = new MockDelegate();
 
-        config = new SegmentRoutingConfig();
+        config = new SegmentRoutingDeviceConfig();
         config.init(subject, key, jsonNode, mapper, delegate);
     }
 
diff --git a/core/api/src/main/java/org/onosproject/net/PortNumber.java b/core/api/src/main/java/org/onosproject/net/PortNumber.java
index da34944..db1ad9b 100644
--- a/core/api/src/main/java/org/onosproject/net/PortNumber.java
+++ b/core/api/src/main/java/org/onosproject/net/PortNumber.java
@@ -44,8 +44,9 @@
     static final long NORMAL_NUMBER = -6L;
     static final long FLOOD_NUMBER = -5L;
     static final long ALL_NUMBER = -4L;
-    static final long LOCAL_NUMBER = -2L;
     static final long CONTROLLER_NUMBER = -3L;
+    static final long LOCAL_NUMBER = -2L;
+    static final long ANY_NUMBER = -1L;
 
     /**
      * Logical PortNumbers.
@@ -57,7 +58,8 @@
         FLOOD(FLOOD_NUMBER),
         ALL(ALL_NUMBER),
         LOCAL(LOCAL_NUMBER),
-        CONTROLLER(CONTROLLER_NUMBER);
+        CONTROLLER(CONTROLLER_NUMBER),
+        ANY(ANY_NUMBER);
 
         private final long number;
         private final PortNumber instance;
@@ -88,6 +90,7 @@
     public static final PortNumber ALL = new PortNumber(ALL_NUMBER);
     public static final PortNumber LOCAL = new PortNumber(LOCAL_NUMBER);
     public static final PortNumber CONTROLLER = new PortNumber(CONTROLLER_NUMBER);
+    public static final PortNumber ANY = new PortNumber(ANY_NUMBER);
 
     // lazily populated Logical port number to PortNumber
     static final Supplier<Map<Long, Logical>> LOGICAL = Suppliers.memoize(() -> {
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
index 9889967..1fafabc 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
@@ -25,6 +25,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import com.google.common.collect.ImmutableList;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.IpPrefix;
@@ -288,6 +289,11 @@
                                                  VlanIdCriterion vidCriterion,
                                                  VlanId assignedVlan,
                                                  ApplicationId applicationId) {
+        // Consider PortNumber.ANY as wildcard. Match ETH_DST only
+        if (portCriterion != null && portCriterion.port() == PortNumber.ANY) {
+            return processEthDstOnlyFilter(ethCriterion, applicationId);
+        }
+
         //handling untagged packets via assigned VLAN
         if (vidCriterion.vlanId() == VlanId.NONE) {
             vidCriterion = (VlanIdCriterion) Criteria.matchVlanId(assignedVlan);
@@ -354,6 +360,32 @@
         return rules;
     }
 
+    @Override
+    protected List<FlowRule> processEthDstOnlyFilter(EthCriterion ethCriterion,
+            ApplicationId applicationId) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        selector.matchEthDst(ethCriterion.mac());
+        /*
+         * Note: CpqD switches do not handle MPLS-related operation properly
+         * for a packet with VLAN tag. We pop VLAN here as a workaround.
+         * Side effect: HostService learns redundant hosts with same MAC but
+         * different VLAN. No known side effect on the network reachability.
+         */
+        treatment.popVlan();
+        treatment.transition(UNICAST_ROUTING_TABLE);
+        FlowRule rule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(DEFAULT_PRIORITY)
+                .fromApp(applicationId)
+                .makePermanent()
+                .forTable(TMAC_TABLE).build();
+        return ImmutableList.<FlowRule>builder().add(rule).build();
+    }
+
     /*
      * Cpqd emulation allows MPLS ecmp.
      *
@@ -600,6 +632,7 @@
                     log.warn("Cannot process instruction in versatile fwd {}", ins);
                 }
             }
+            ttBuilder.wipeDeferred();
         }
         if (fwd.nextId() != null) {
             // overide case
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2VlanPipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2VlanPipeline.java
index cfeb14c..6b43a2a 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2VlanPipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2VlanPipeline.java
@@ -78,6 +78,11 @@
                                                  VlanIdCriterion vidCriterion,
                                                  VlanId assignedVlan,
                                                  ApplicationId applicationId) {
+        // Consider PortNumber.ANY as wildcard. Match ETH_DST only
+        if (portCriterion != null && portCriterion.port() == PortNumber.ANY) {
+            return processEthDstOnlyFilter(ethCriterion, applicationId);
+        }
+
         //handling untagged packets via assigned VLAN
         if (vidCriterion.vlanId() == VlanId.NONE) {
             vidCriterion = (VlanIdCriterion) Criteria.matchVlanId(assignedVlan);
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java
index 828b778..883182b 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2GroupHandler.java
@@ -6,6 +6,7 @@
 import com.google.common.cache.RemovalCause;
 import com.google.common.cache.RemovalNotification;
 import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.MplsLabel;
 import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
@@ -95,7 +96,7 @@
             Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d"));
 
     // index number for group creation
-    private AtomicInteger l3vpnindex = new AtomicInteger(0);
+    private AtomicInteger l3VpnIndex = new AtomicInteger(0);
 
     // local stores for port-vlan mapping
     protected Map<PortNumber, VlanId> port2Vlan = new ConcurrentHashMap<>();
@@ -332,12 +333,14 @@
         VlanId vlanid = null;
         long portNum = 0;
         boolean setVlan = false, popVlan = false;
+        MacAddress dstMac = MacAddress.ZERO;
         for (Instruction ins : treatment.allInstructions()) {
             if (ins.type() == Instruction.Type.L2MODIFICATION) {
                 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
                 switch (l2ins.subtype()) {
                     case ETH_DST:
-                        outerTtb.setEthDst(((L2ModificationInstruction.ModEtherInstruction) l2ins).mac());
+                        dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
+                        outerTtb.setEthDst(dstMac);
                         break;
                     case ETH_SRC:
                         outerTtb.setEthSrc(((L2ModificationInstruction.ModEtherInstruction) l2ins).mac());
@@ -430,8 +433,11 @@
                     mplsgroupkey, nextId);
         } else {
             // outer group is L3Unicast
-            int l3groupId = L3_UNICAST_TYPE | (int) portNum;
-            int l3gk = L3_UNICAST_TYPE | (TYPE_MASK & (deviceId.hashCode() << 8 | (int) portNum));
+            int l3groupId = L3_UNICAST_TYPE |
+                    (TYPE_MASK & (int) (dstMac.toLong() & 0xffff) << 6 | (int) portNum);
+            int l3gk = L3_UNICAST_TYPE |
+                    (TYPE_MASK & (deviceId.hashCode() << 22 |
+                            (int) (dstMac.toLong() & 0xffff) << 6 | (int) portNum));
             final GroupKey l3groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3gk));
             outerTtb.group(new DefaultGroupId(l2groupId));
             // create the l3unicast group description to wait for the
@@ -734,8 +740,8 @@
                                 onelabelGroupInfo.outerGrpDesc.givenGroupId()));
                 GroupBucket l3vpnGrpBkt  =
                         DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
-                int l3vpngroupId = MPLS_L3VPN_SUBTYPE | l3vpnindex.incrementAndGet();
-                int l3vpngk = MPLS_L3VPN_SUBTYPE | nextObj.id() << 12 | l3vpnindex.get();
+                int l3vpngroupId = MPLS_L3VPN_SUBTYPE | l3VpnIndex.incrementAndGet();
+                int l3vpngk = MPLS_L3VPN_SUBTYPE | nextObj.id() << 12 | l3VpnIndex.get();
                 GroupKey l3vpngroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3vpngk));
                 GroupDescription l3vpnGroupDesc =
                         new DefaultGroupDescription(
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
index 9f52ac0..382d978 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
@@ -27,6 +27,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import com.google.common.collect.ImmutableList;
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpPrefix;
@@ -553,6 +554,11 @@
                                                  VlanIdCriterion vidCriterion,
                                                  VlanId assignedVlan,
                                                  ApplicationId applicationId) {
+        // Consider PortNumber.ANY as wildcard. Match ETH_DST only
+        if (portCriterion != null && portCriterion.port() == PortNumber.ANY) {
+            return processEthDstOnlyFilter(ethCriterion, applicationId);
+        }
+
         //handling untagged packets via assigned VLAN
         if (vidCriterion.vlanId() == VlanId.NONE) {
             vidCriterion = (VlanIdCriterion) Criteria.matchVlanId(assignedVlan);
@@ -611,6 +617,24 @@
         return rules;
     }
 
+    protected List<FlowRule> processEthDstOnlyFilter(EthCriterion ethCriterion,
+            ApplicationId applicationId) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        selector.matchEthDst(ethCriterion.mac());
+        treatment.transition(UNICAST_ROUTING_TABLE);
+        FlowRule rule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(DEFAULT_PRIORITY)
+                .fromApp(applicationId)
+                .makePermanent()
+                .forTable(TMAC_TABLE).build();
+        return ImmutableList.<FlowRule>builder().add(rule).build();
+    }
+
     private Collection<FlowRule> processForward(ForwardingObjective fwd) {
         switch (fwd.flag()) {
             case SPECIFIC:
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
index 5c65e11..50b439f 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
@@ -23,6 +23,7 @@
 import com.google.common.cache.RemovalCause;
 import com.google.common.cache.RemovalNotification;
 
+import com.google.common.collect.ImmutableList;
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.MacAddress;
@@ -504,6 +505,7 @@
                     fwd.treatment().allInstructions().get(0).type() == Instruction.Type.OUTPUT) {
                 OutputInstruction o = (OutputInstruction) fwd.treatment().allInstructions().get(0);
                 if (o.port() == PortNumber.CONTROLLER) {
+                    treatmentBuilder.popVlan();
                     treatmentBuilder.punt();
                 } else {
                     treatmentBuilder.add(o);
@@ -780,6 +782,10 @@
                                        FilteringObjective filt,
                                        VlanId assignedVlan,
                                        ApplicationId applicationId) {
+        if (vlanIdCriterion == null) {
+            return processEthDstOnlyFilter(ethCriterion, applicationId, filt.priority());
+        }
+
         //handling untagged packets via assigned VLAN
         if (vlanIdCriterion.vlanId() == VlanId.NONE) {
             vlanIdCriterion = (VlanIdCriterion) Criteria.matchVlanId(assignedVlan);
@@ -823,6 +829,24 @@
         return rules;
     }
 
+    protected List<FlowRule> processEthDstOnlyFilter(EthCriterion ethCriterion,
+            ApplicationId applicationId, int priority) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        selector.matchEthDst(ethCriterion.mac());
+        treatment.transition(TABLE_IPV4_UNICAST);
+        FlowRule rule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .withPriority(priority)
+                .fromApp(applicationId)
+                .makePermanent()
+                .forTable(TABLE_TMAC).build();
+        return ImmutableList.<FlowRule>builder().add(rule).build();
+    }
+
     protected List<FlowRule> processVlanIdFilter(VlanIdCriterion vlanIdCriterion,
                                                  FilteringObjective filt,
                                                  VlanId assignedVlan,
diff --git a/tools/package/config/samples/network-cfg-fabric2x2-default-route.json b/tools/package/config/samples/network-cfg-fabric2x2-default-route.json
new file mode 100644
index 0000000..f9e49cc
--- /dev/null
+++ b/tools/package/config/samples/network-cfg-fabric2x2-default-route.json
@@ -0,0 +1,119 @@
+{
+    "ports" : {
+    "of:0000000000000001/3" : {
+        "interfaces" : [
+        {
+            "ips" : [ "10.0.1.254/24" ],
+            "vlan" : "-1"
+        }
+        ]
+    },
+    "of:0000000000000001/4" : {
+        "interfaces" : [
+        {
+            "ips" : [ "10.0.1.254/24", "200.0.0.200/32" ],
+            "vlan" : "-1"
+        }
+        ]
+    },
+    "of:0000000000000002/3" : {
+        "interfaces" : [
+        {
+            "ips" : [ "10.0.2.254/24" ],
+            "vlan" : "-1"
+        }
+        ]
+    },
+    "of:0000000000000002/4" : {
+        "interfaces" : [
+        {
+            "ips" : [ "10.0.2.254/24", "0.0.0.0/0" ],
+            "vlan" : "-1"
+        }
+        ]
+    }
+    },
+    "devices" : {
+        "of:0000000000000001" : {
+            "segmentrouting" : {
+                "name" : "Leaf-R1",
+                "nodeSid" : 101,
+                "routerIp" : "10.0.1.254",
+                "routerMac" : "00:00:00:00:01:80",
+                "isEdgeRouter" : true,
+                "adjacencySids" : []
+            }
+        },
+        "of:0000000000000002" : {
+            "segmentrouting" : {
+                "name" : "Leaf-R2",
+                "nodeSid" : 102,
+                "routerIp" : "10.0.2.254",
+                "routerMac" : "00:00:00:00:02:80",
+                "isEdgeRouter" : true,
+                "adjacencySids" : []
+            }
+        },
+        "of:0000000000000191" : {
+            "segmentrouting" : {
+                "name" : "Spine-R1",
+                "nodeSid" : 103,
+                "routerIp" : "192.168.0.11",
+                "routerMac" : "00:00:01:00:11:80",
+                "isEdgeRouter" : false,
+                "adjacencySids" : []
+            }
+        },
+        "of:0000000000000192" : {
+            "segmentrouting" : {
+                "name" : "Spine-R2",
+                "nodeSid" : 104,
+                "routerIp" : "192.168.0.22",
+                "routerMac" : "00:00:01:00:22:80",
+                "isEdgeRouter" : false,
+                "adjacencySids" : []
+            }
+        }
+    },
+    "hosts" : {
+        "00:00:00:00:00:01/-1" : {
+            "basic": {
+                "ips": ["10.0.1.1"],
+                "location": "of:0000000000000001/3"
+            }
+        },
+        "00:00:00:00:00:02/-1" : {
+            "basic": {
+                "ips": ["10.0.1.2"],
+                "location": "of:0000000000000001/4"
+            }
+        },
+        "00:00:00:00:00:03/-1" : {
+            "basic": {
+                "ips": ["10.0.2.1"],
+                "location": "of:0000000000000002/3"
+            }
+        },
+        "00:00:00:00:00:04/-1" : {
+            "basic": {
+                "ips": ["10.0.2.2"],
+                "location": "of:0000000000000002/4"
+            }
+        },
+        "00:00:00:aa:00:01/-1" : {
+            "basic": {
+                "ips": ["200.0.0.200"],
+                "location": "of:0000000000000001/4"
+            }
+        }
+    },
+    "apps" : {
+        "org.onosproject.segmentrouting" : {
+            "segmentrouting" : {
+                "vRouterMacs" : [
+                    "00:00:00:aa:00:02"
+                ]
+            }
+        }
+    }
+}
diff --git a/tools/package/config/samples/network-cfg-fabric2x2-xconnect.json b/tools/package/config/samples/network-cfg-fabric2x2-xconnect.json
index ef73261..3505fb0 100644
--- a/tools/package/config/samples/network-cfg-fabric2x2-xconnect.json
+++ b/tools/package/config/samples/network-cfg-fabric2x2-xconnect.json
@@ -86,25 +86,25 @@
         }
     },
     "hosts" : {
-        "00:00:00:00:00:01/4093" : {
+        "00:00:00:00:00:01/-1" : {
             "basic": {
                 "ips": ["10.0.1.1"],
                 "location": "of:0000000000000001/3"
             }
         },
-        "00:00:00:00:00:02/4093" : {
+        "00:00:00:00:00:02/-1" : {
             "basic": {
                 "ips": ["10.0.1.2"],
                 "location": "of:0000000000000001/4"
             }
         },
-        "00:00:00:00:00:03/4093" : {
+        "00:00:00:00:00:03/-1" : {
             "basic": {
                 "ips": ["10.0.2.1"],
                 "location": "of:0000000000000002/3"
             }
         },
-        "00:00:00:00:00:04/4093" : {
+        "00:00:00:00:00:04/-1" : {
             "basic": {
                 "ips": ["10.0.2.2"],
                 "location": "of:0000000000000002/4"
diff --git a/tools/package/config/samples/network-cfg-fabric4x4.json b/tools/package/config/samples/network-cfg-fabric4x4.json
index 958e124..80b349b 100644
--- a/tools/package/config/samples/network-cfg-fabric4x4.json
+++ b/tools/package/config/samples/network-cfg-fabric4x4.json
@@ -148,49 +148,49 @@
         }
     },
     "hosts" : {
-        "00:00:00:00:00:01/4093" : {
+        "00:00:00:00:00:01/-1" : {
             "basic": {
                 "ips": ["10.0.1.1"],
                 "location": "of:0000000000000001/5"
             }
         },
-        "00:00:00:00:00:02/4093" : {
+        "00:00:00:00:00:02/-1" : {
             "basic": {
                 "ips": ["10.0.1.2"],
                 "location": "of:0000000000000001/6"
             }
         },
-        "00:00:00:00:00:03/4093" : {
+        "00:00:00:00:00:03/-1" : {
             "basic": {
                 "ips": ["10.0.2.1"],
                 "location": "of:0000000000000002/5"
             }
         },
-        "00:00:00:00:00:04/4093" : {
+        "00:00:00:00:00:04/-1" : {
             "basic": {
                 "ips": ["10.0.2.2"],
                 "location": "of:0000000000000002/6"
             }
         },
-        "00:00:00:00:00:05/4093" : {
+        "00:00:00:00:00:05/-1" : {
             "basic": {
                 "ips": ["10.0.3.1"],
                 "location": "of:0000000000000003/5"
             }
         },
-        "00:00:00:00:00:06/4093" : {
+        "00:00:00:00:00:06/-1" : {
             "basic": {
                 "ips": ["10.0.3.2"],
                 "location": "of:0000000000000003/6"
             }
         },
-        "00:00:00:00:00:07/4093" : {
+        "00:00:00:00:00:07/-1" : {
             "basic": {
                 "ips": ["10.0.4.1"],
                 "location": "of:0000000000000004/5"
             }
         },
-        "00:00:00:00:00:08/4093" : {
+        "00:00:00:00:00:08/-1" : {
             "basic": {
                 "ips": ["10.0.4.2"],
                 "location": "of:0000000000000004/6"