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/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index a8d16e8..d59fb12 100644
--- a/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/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/src/main/java/org/onosproject/segmentrouting/NetworkConfigEventHandler.java b/src/main/java/org/onosproject/segmentrouting/NetworkConfigEventHandler.java
new file mode 100644
index 0000000..afb7ec9
--- /dev/null
+++ b/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/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 6017e8b..10ac6d6 100644
--- a/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/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/src/main/java/org/onosproject/segmentrouting/SRObjectiveContext.java b/src/main/java/org/onosproject/segmentrouting/SRObjectiveContext.java
new file mode 100644
index 0000000..8dd75de
--- /dev/null
+++ b/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/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 592c0b1..32e03de 100644
--- a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/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/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
index 44bd453..f82ad3e 100644
--- a/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
+++ b/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/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index 3d2d337..db4bc63 100644
--- a/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/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/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfig.java b/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingAppConfig.java
new file mode 100644
index 0000000..e39fb18
--- /dev/null
+++ b/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/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingConfig.java b/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
similarity index 87%
rename from src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingConfig.java
rename to src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
index 64486c6..10c69ca 100644
--- a/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingConfig.java
+++ b/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 {