Recover dual-homed host failover using pair link

In addition,
- Block ARP/NDP on pair port from being sent to controller
- Block DAD on pair port from flooded back to the originated host
- Minor refactoring

Change-Id: I3d697a06cb7ed3b56baa5d490197c155fe6969f0
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 0ca1ba9..f8428a7 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -39,6 +39,8 @@
 import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.Sets;
+
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -100,42 +102,77 @@
     }
 
     private void processHostRemoved(Host host) {
-        MacAddress mac = host.mac();
-        VlanId vlanId = host.vlan();
+        MacAddress hostMac = host.mac();
+        VlanId hostVlanId = host.vlan();
         Set<HostLocation> locations = host.locations();
         Set<IpAddress> ips = host.ipAddresses();
-        log.info("Host {}/{} is removed from {}", mac, vlanId, locations);
+        log.info("Host {}/{} is removed from {}", hostMac, hostVlanId, locations);
 
         locations.forEach(location -> {
-            processBridgingRule(location.deviceId(), location.port(), mac, vlanId, true);
+            processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
             ips.forEach(ip ->
-                processRoutingRule(location.deviceId(), location.port(), mac, vlanId, ip, true)
+                processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
             );
+
+            // Also remove redirection flows on the pair device if exists.
+            Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
+            Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
+            if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
+                // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
+                //       when the host is untagged
+                VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
+
+                processBridgingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId, true);
+                ips.forEach(ip ->
+                        processRoutingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId,
+                                ip, true));
+            }
         });
     }
 
     void processHostMovedEvent(HostEvent event) {
-        MacAddress mac = event.subject().mac();
-        VlanId vlanId = event.subject().vlan();
+        MacAddress hostMac = event.subject().mac();
+        VlanId hostVlanId = event.subject().vlan();
         Set<HostLocation> prevLocations = event.prevSubject().locations();
         Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
         Set<HostLocation> newLocations = event.subject().locations();
         Set<IpAddress> newIps = event.subject().ipAddresses();
-        log.info("Host {}/{} is moved from {} to {}", mac, vlanId, prevLocations, newLocations);
+        log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
 
         Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
                 .collect(Collectors.toSet());
 
         // For each old location
         Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
-            // TODO Switch to backup link when pair device is configured
+            // Remove routing rules for old IPs
+            Sets.difference(prevIps, newIps).forEach(ip ->
+                    processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
+                            ip, true)
+            );
+
+            // Redirect the flows to pair link if configured
+            // Note: Do not continue removing any rule
+            Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
+            Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
+            if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
+                    .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
+                // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
+                //       when the host is untagged
+                VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
+
+                processBridgingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId, false);
+                newIps.forEach(ip ->
+                        processRoutingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId,
+                            ip, false));
+                return;
+            }
 
             // Remove bridging rule and routing rules for unchanged IPs if the host moves from a switch to another.
             // Otherwise, do not remove and let the adding part update the old flow
             if (!newDeviceIds.contains(prevLocation.deviceId())) {
-                processBridgingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId, true);
+                processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
                 Sets.intersection(prevIps, newIps).forEach(ip ->
-                        processRoutingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId,
+                        processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
                                 ip, true)
                 );
             }
@@ -146,12 +183,12 @@
                 VlanId oldAssignedVlan = srManager.getInternalVlanId(prevLocation);
                 VlanId newAssignedVlan = srManager.getInternalVlanId(newLocation);
                 // Host is tagged and the new location has the host vlan in vlan-tagged
-                return srManager.getTaggedVlanId(newLocation).contains(vlanId) ||
+                return srManager.getTaggedVlanId(newLocation).contains(hostVlanId) ||
                         (oldAssignedVlan != null && newAssignedVlan != null &&
                         // Host is untagged and the new location has the same assigned vlan
                         oldAssignedVlan.equals(newAssignedVlan));
             })) {
-                processBridgingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId, true);
+                processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
             }
 
             // Remove routing rules for unchanged IPs if none of the subnet of new location contains
@@ -159,23 +196,17 @@
             Sets.intersection(prevIps, newIps).forEach(ip -> {
                 if (newLocations.stream().noneMatch(newLocation ->
                         srManager.deviceConfiguration.inSameSubnet(newLocation, ip))) {
-                    processRoutingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId,
+                    processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
                             ip, true);
                 }
             });
-
-            // Remove routing rules for old IPs
-            Sets.difference(prevIps, newIps).forEach(ip ->
-                processRoutingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId,
-                        ip, true)
-            );
         });
 
         // For each new location, add all new IPs.
         Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
-            processBridgingRule(newLocation.deviceId(), newLocation.port(), mac, vlanId, false);
+            processBridgingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId, false);
             newIps.forEach(ip ->
-                    processRoutingRule(newLocation.deviceId(), newLocation.port(), mac, vlanId,
+                    processRoutingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId,
                             ip, false)
             );
         });
@@ -183,13 +214,13 @@
         // For each unchanged location, add new IPs and remove old IPs.
         Sets.intersection(newLocations, prevLocations).forEach(unchangedLocation -> {
             Sets.difference(prevIps, newIps).forEach(ip ->
-                    processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), mac,
-                            vlanId, ip, true)
+                    processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
+                            hostVlanId, ip, true)
             );
 
             Sets.difference(newIps, prevIps).forEach(ip ->
-                    processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), mac,
-                        vlanId, ip, false)
+                    processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
+                        hostVlanId, ip, 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 9ff5bd0..89aa4a3 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -87,7 +87,7 @@
      *
      * @param srManager segment routing manager reference
      */
-    public RoutingRulePopulator(SegmentRoutingManager srManager) {
+    RoutingRulePopulator(SegmentRoutingManager srManager) {
         this.srManager = srManager;
         this.config = checkNotNull(srManager.deviceConfiguration);
         this.rulePopulationCounter = new AtomicLong(0);
@@ -96,7 +96,7 @@
     /**
      * Resets the population counter.
      */
-    public void resetCounter() {
+    void resetCounter() {
         rulePopulationCounter.set(0);
     }
 
@@ -105,7 +105,7 @@
      *
      * @return number of rules
      */
-    public long getCounter() {
+    long getCounter() {
         return rulePopulationCounter.get();
     }
 
@@ -229,14 +229,9 @@
             } else if (nativeVlan != null) {
                 mbuilder.matchVlanId(nativeVlan);
             } else {
-                // TODO: This check is turned off for now since vRouter still assumes that
-                // hosts are internally tagged with INTERNAL_VLAN.
-                // We should turn this back on when we move forward to the bridging CPR approach.
-                //
-                //log.warn("Untagged nexthop {}/{} is not allowed on {} without untagged or native vlan",
-                //        hostMac, hostVlanId, connectPoint);
-                //return null;
-                mbuilder.matchVlanId(INTERNAL_VLAN);
+                log.warn("Untagged nexthop {}/{} is not allowed on {} without untagged or native vlan",
+                        hostMac, hostVlanId, connectPoint);
+                return null;
             }
         } else {
             log.warn("Tagged nexthop {}/{} is not allowed on {} without VLAN listed"
@@ -278,7 +273,7 @@
      *                 map for destSw2.
      * @return true if all rules are set successfully, false otherwise
      */
-    public boolean populateIpRuleForSubnet(DeviceId targetSw, Set<IpPrefix> subnets,
+    boolean populateIpRuleForSubnet(DeviceId targetSw, Set<IpPrefix> subnets,
             DeviceId destSw1, DeviceId destSw2, Map<DeviceId, Set<DeviceId>> nextHops) {
         for (IpPrefix subnet : subnets) {
             if (!populateIpRuleForRouter(targetSw, subnet, destSw1, destSw2, nextHops)) {
@@ -294,7 +289,7 @@
      * @param subnets subnet being removed
      * @return true if all rules are removed successfully, false otherwise
      */
-    public boolean revokeIpRuleForSubnet(Set<IpPrefix> subnets) {
+    boolean revokeIpRuleForSubnet(Set<IpPrefix> subnets) {
         for (IpPrefix subnet : subnets) {
             if (!revokeIpRuleForRouter(subnet)) {
                 return false;
@@ -319,7 +314,7 @@
      *                  should not be an entry for destSw2 in this map.
      * @return true if all rules are set successfully, false otherwise
      */
-    public boolean populateIpRuleForRouter(DeviceId targetSw,
+    private boolean populateIpRuleForRouter(DeviceId targetSw,
                                            IpPrefix ipPrefix, DeviceId destSw1,
                                            DeviceId destSw2,
                                            Map<DeviceId, Set<DeviceId>> nextHops) {
@@ -427,7 +422,7 @@
      * @param ipPrefix the IP address of the destination router
      * @return true if all rules are removed successfully, false otherwise
      */
-    public boolean revokeIpRuleForRouter(IpPrefix ipPrefix) {
+    private boolean revokeIpRuleForRouter(IpPrefix ipPrefix) {
         TrafficSelector.Builder sbuilder = buildIpSelectorFromIpPrefix(ipPrefix);
         TrafficSelector selector = sbuilder.build();
         TrafficTreatment dummyTreatment = DefaultTrafficTreatment.builder().build();
@@ -446,9 +441,9 @@
                 (objective, error) ->
                         log.warn("Failed to revoke IP rule for router {}: {}", ipPrefix, error));
 
-        srManager.deviceService.getAvailableDevices().forEach(device -> {
-            srManager.flowObjectiveService.forward(device.id(), fwdBuilder.remove(context));
-        });
+        srManager.deviceService.getAvailableDevices().forEach(device ->
+            srManager.flowObjectiveService.forward(device.id(), fwdBuilder.remove(context))
+        );
 
         return true;
     }
@@ -568,7 +563,7 @@
      * @param routerIp the router ip
      * @return true if all rules are set successfully, false otherwise
      */
-    public boolean populateMplsRule(DeviceId targetSwId, DeviceId destSwId,
+    boolean populateMplsRule(DeviceId targetSwId, DeviceId destSwId,
                                     Set<DeviceId> nextHops,
                                     IpAddress routerIp) {
 
@@ -585,7 +580,7 @@
         }
 
         List<ForwardingObjective> fwdObjs = new ArrayList<>();
-        Collection<ForwardingObjective> fwdObjsMpls = Collections.emptyList();
+        Collection<ForwardingObjective> fwdObjsMpls;
         // Generates the transit rules used by the standard "routing".
         fwdObjsMpls = handleMpls(targetSwId, destSwId, nextHops, segmentId, routerIp, true);
         if (fwdObjsMpls.isEmpty()) {
@@ -594,11 +589,13 @@
         fwdObjs.addAll(fwdObjsMpls);
         // Generates the transit rules used by the MPLS Pwaas. For now it is
         // the only case !BoS supported.
-        /*fwdObjsMpls = handleMpls(targetSwId, destSwId, nextHops, segmentId, routerIp, false);
+        /*
+        fwdObjsMpls = handleMpls(targetSwId, destSwId, nextHops, segmentId, routerIp, false);
         if (fwdObjsMpls.isEmpty()) {
             return false;
         }
-        fwdObjs.addAll(fwdObjsMpls);*/
+        fwdObjs.addAll(fwdObjsMpls);
+        */
 
         for (ForwardingObjective fwdObj : fwdObjs) {
             log.debug("Sending MPLS fwd obj {} for SID {}-> next {} in sw: {}",
@@ -699,7 +696,7 @@
      * @param deviceId  the switch dpid for the router
      * @return PortFilterInfo information about the processed ports
      */
-    public PortFilterInfo populateVlanMacFilters(DeviceId deviceId) {
+    PortFilterInfo populateVlanMacFilters(DeviceId deviceId) {
         log.debug("Installing per-port filtering objective for untagged "
                 + "packets in device {}", deviceId);
 
@@ -736,7 +733,7 @@
      * @param install true to install the filtering objective, false to remove
      * @return true if no errors occurred during the build of the filtering objective
      */
-    public boolean processSinglePortFilters(DeviceId deviceId, PortNumber portnum, boolean install) {
+    boolean processSinglePortFilters(DeviceId deviceId, PortNumber portnum, boolean install) {
         ConnectPoint connectPoint = new ConnectPoint(deviceId, portnum);
         VlanId untaggedVlan = srManager.getUntaggedVlanId(connectPoint);
         Set<VlanId> taggedVlans = srManager.getTaggedVlanId(connectPoint);
@@ -825,7 +822,7 @@
      *
      * @param deviceId the switch dpid for the router
      */
-    public void populateIpPunts(DeviceId deviceId) {
+    void populateIpPunts(DeviceId deviceId) {
         Ip4Address routerIpv4, pairRouterIpv4 = null;
         Ip6Address routerIpv6, pairRouterIpv6 = null;
         try {
@@ -900,7 +897,7 @@
      *
      * @param deviceId the switch dpid for the router
      */
-    public void populateArpNdpPunts(DeviceId deviceId) {
+    void populateArpNdpPunts(DeviceId deviceId) {
         // We are not the master just skip.
         if (!srManager.mastershipService.isLocalMaster(deviceId)) {
             log.debug("Not installing ARP/NDP punts - not the master for dev:{} ",
@@ -908,60 +905,120 @@
             return;
         }
 
+        ForwardingObjective fwdObj;
         // We punt all ARP packets towards the controller.
-        ForwardingObjective puntFwd = puntArpFwdObjective()
+        fwdObj = arpFwdObjective(null, true, PacketPriority.CONTROL.priorityValue())
                 .add(new ObjectiveContext() {
                     @Override
                     public void onError(Objective objective, ObjectiveError error) {
-                        log.warn("Failed to install packet request for ARP to {}: {}",
+                        log.warn("Failed to install forwarding objective to punt ARP to {}: {}",
                                  deviceId, error);
                     }
                 });
-        srManager.flowObjectiveService.forward(deviceId, puntFwd);
+        srManager.flowObjectiveService.forward(deviceId, fwdObj);
 
         // We punt all NDP packets towards the controller.
-        puntFwd = puntNdpFwdObjective()
+        fwdObj = ndpFwdObjective(null, true, PacketPriority.CONTROL.priorityValue())
                 .add(new ObjectiveContext() {
                     @Override
                     public void onError(Objective objective, ObjectiveError error) {
-                        log.warn("Failed to install packet request for NDP to {}: {}",
+                        log.warn("Failed to install forwarding objective to punt NDP to {}: {}",
                                  deviceId, error);
                     }
                 });
-        srManager.flowObjectiveService.forward(deviceId, puntFwd);
+        srManager.flowObjectiveService.forward(deviceId, fwdObj);
+
+        srManager.getPairLocalPorts(deviceId).ifPresent(port -> {
+            ForwardingObjective pairFwdObj;
+            // Do not punt ARP packets from pair port
+            pairFwdObj = arpFwdObjective(port, false, PacketPriority.CONTROL.priorityValue() + 1)
+                    .add(new ObjectiveContext() {
+                        @Override
+                        public void onError(Objective objective, ObjectiveError error) {
+                            log.warn("Failed to install forwarding objective to ignore ARP to {}: {}",
+                                    deviceId, error);
+                        }
+                    });
+            srManager.flowObjectiveService.forward(deviceId, pairFwdObj);
+
+            // Do not punt NDP packets from pair port
+            pairFwdObj = ndpFwdObjective(port, false, PacketPriority.CONTROL.priorityValue() + 1)
+                    .add(new ObjectiveContext() {
+                        @Override
+                        public void onError(Objective objective, ObjectiveError error) {
+                            log.warn("Failed to install forwarding objective to ignore ARP to {}: {}",
+                                    deviceId, error);
+                        }
+                    });
+            srManager.flowObjectiveService.forward(deviceId, pairFwdObj);
+
+            // Do not forward DAD packets from pair port
+            pairFwdObj = dad6FwdObjective(port, PacketPriority.CONTROL.priorityValue() + 2)
+                    .add(new ObjectiveContext() {
+                        @Override
+                        public void onError(Objective objective, ObjectiveError error) {
+                            log.warn("Failed to install forwarding objective to drop DAD to {}: {}",
+                                    deviceId, error);
+                        }
+                    });
+            srManager.flowObjectiveService.forward(deviceId, pairFwdObj);
+        });
     }
 
-    private ForwardingObjective.Builder fwdObjBuilder(TrafficSelector selector) {
-
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-        tBuilder.punt();
-
+    private ForwardingObjective.Builder fwdObjBuilder(TrafficSelector selector,
+                                                      TrafficTreatment treatment, int priority) {
         return DefaultForwardingObjective.builder()
-                .withPriority(PacketPriority.CONTROL.priorityValue())
+                .withPriority(priority)
                 .withSelector(selector)
                 .fromApp(srManager.appId)
                 .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withTreatment(tBuilder.build())
+                .withTreatment(treatment)
                 .makePermanent();
     }
 
-    private ForwardingObjective.Builder puntArpFwdObjective() {
-
+    private ForwardingObjective.Builder arpFwdObjective(PortNumber port, boolean punt, int priority) {
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
         sBuilder.matchEthType(TYPE_ARP);
+        if (port != null) {
+            sBuilder.matchInPort(port);
+        }
 
-        return fwdObjBuilder(sBuilder.build());
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        if (punt) {
+            tBuilder.punt();
+        }
+        return fwdObjBuilder(sBuilder.build(), tBuilder.build(), priority);
     }
 
-    private ForwardingObjective.Builder puntNdpFwdObjective() {
-
+    private ForwardingObjective.Builder ndpFwdObjective(PortNumber port, boolean punt, int priority) {
         TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
         sBuilder.matchEthType(TYPE_IPV6)
                 .matchIPProtocol(PROTOCOL_ICMP6)
-                .matchIcmpv6Type(NEIGHBOR_SOLICITATION)
-                .build();
+                .matchIcmpv6Type(NEIGHBOR_SOLICITATION);
+        if (port != null) {
+            sBuilder.matchInPort(port);
+        }
 
-        return fwdObjBuilder(sBuilder.build());
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        if (punt) {
+            tBuilder.punt();
+        }
+        return fwdObjBuilder(sBuilder.build(), tBuilder.build(), priority);
+    }
+
+    private ForwardingObjective.Builder dad6FwdObjective(PortNumber port, int priority) {
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        sBuilder.matchEthType(TYPE_IPV6)
+                .matchIPv6Src(Ip6Address.ZERO.toIpPrefix())
+                .matchIPProtocol(PROTOCOL_ICMP6)
+                .matchIcmpv6Type(NEIGHBOR_SOLICITATION);
+        if (port != null) {
+            sBuilder.matchInPort(port);
+        }
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        tBuilder.wipeDeferred();
+        return fwdObjBuilder(sBuilder.build(), tBuilder.build(), priority);
     }
 
     /**
@@ -971,7 +1028,7 @@
      *
      * @param deviceId switch ID to set the rules
      */
-    public void populateSubnetBroadcastRule(DeviceId deviceId) {
+    void populateSubnetBroadcastRule(DeviceId deviceId) {
         srManager.getVlanPortMap(deviceId).asMap().forEach((vlanId, ports) -> {
             int nextId = srManager.getVlanNextObjectiveId(deviceId, vlanId);
 
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 9ac8d1a..69a8079 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -103,6 +103,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -190,7 +191,7 @@
     private AppConfigHandler appCfgHandler = null;
     XConnectHandler xConnectHandler = null;
     private McastHandler mcastHandler = null;
-    HostHandler hostHandler = null;
+    private HostHandler hostHandler = null;
     private RouteHandler routeHandler = null;
     private SegmentRoutingNeighbourDispatcher neighbourHandler = null;
     private L2TunnelHandler l2TunnelHandler = null;
@@ -290,7 +291,7 @@
                 }
             };
 
-    private Object threadSchedulerLock = new Object();
+    private static final Object THREAD_SCHED_LOCK = new Object();
     private static int numOfEventsQueued = 0;
     private static int numOfEventsExecuted = 0;
     private static int numOfHandlerExecution = 0;
@@ -605,7 +606,7 @@
      * @param connectPoint connect point
      * @return untagged VLAN or null if not configured
      */
-    public VlanId getUntaggedVlanId(ConnectPoint connectPoint) {
+    VlanId getUntaggedVlanId(ConnectPoint connectPoint) {
         return interfaceService.getInterfacesByPort(connectPoint).stream()
                 .filter(intf -> !intf.vlanUntagged().equals(VlanId.NONE))
                 .map(Interface::vlanUntagged)
@@ -621,11 +622,11 @@
      * @param connectPoint connect point
      * @return tagged VLAN or empty set if not configured
      */
-    public Set<VlanId> getTaggedVlanId(ConnectPoint connectPoint) {
+    Set<VlanId> getTaggedVlanId(ConnectPoint connectPoint) {
         Set<Interface> interfaces = interfaceService.getInterfacesByPort(connectPoint);
         return interfaces.stream()
                 .map(Interface::vlanTagged)
-                .flatMap(vlanIds -> vlanIds.stream())
+                .flatMap(Set::stream)
                 .collect(Collectors.toSet());
     }
 
@@ -638,7 +639,7 @@
      * @param connectPoint connect point
      * @return native VLAN or null if not configured
      */
-    public VlanId getNativeVlanId(ConnectPoint connectPoint) {
+    VlanId getNativeVlanId(ConnectPoint connectPoint) {
         Set<Interface> interfaces = interfaceService.getInterfacesByPort(connectPoint);
         return interfaces.stream()
                 .filter(intf -> !intf.vlanNative().equals(VlanId.NONE))
@@ -656,13 +657,36 @@
      * @param connectPoint connect point
      * @return internal VLAN or null if both vlan-untagged and vlan-native are undefined
      */
-    public VlanId getInternalVlanId(ConnectPoint connectPoint) {
+    VlanId getInternalVlanId(ConnectPoint connectPoint) {
         VlanId untaggedVlanId = getUntaggedVlanId(connectPoint);
         VlanId nativeVlanId = getNativeVlanId(connectPoint);
         return untaggedVlanId != null ? untaggedVlanId : nativeVlanId;
     }
 
     /**
+     * Returns optional pair device ID of given device.
+     *
+     * @param deviceId device ID
+     * @return optional pair device ID. Might be empty if pair device is not configured
+     */
+    Optional<DeviceId> getPairDeviceId(DeviceId deviceId) {
+        SegmentRoutingDeviceConfig deviceConfig =
+                cfgService.getConfig(deviceId, SegmentRoutingDeviceConfig.class);
+        return Optional.ofNullable(deviceConfig).map(SegmentRoutingDeviceConfig::pairDeviceId);
+    }
+    /**
+     * Returns optional pair device local port of given device.
+     *
+     * @param deviceId device ID
+     * @return optional pair device ID. Might be empty if pair device is not configured
+     */
+    Optional<PortNumber> getPairLocalPorts(DeviceId deviceId) {
+        SegmentRoutingDeviceConfig deviceConfig =
+                cfgService.getConfig(deviceId, SegmentRoutingDeviceConfig.class);
+        return Optional.ofNullable(deviceConfig).map(SegmentRoutingDeviceConfig::pairLocalPort);
+    }
+
+    /**
      * Returns vlan port map of given device.
      *
      * @param deviceId device id
@@ -675,9 +699,9 @@
                 .filter(intf -> intf.connectPoint().deviceId().equals(deviceId))
                 .forEach(intf -> {
                     vlanPortMap.put(intf.vlanUntagged(), intf.connectPoint().port());
-                    intf.vlanTagged().forEach(vlanTagged -> {
-                        vlanPortMap.put(vlanTagged, intf.connectPoint().port());
-                    });
+                    intf.vlanTagged().forEach(vlanTagged ->
+                        vlanPortMap.put(vlanTagged, intf.connectPoint().port())
+                    );
                     vlanPortMap.put(intf.vlanNative(), intf.connectPoint().port());
                 });
         vlanPortMap.removeAll(VlanId.NONE);
@@ -694,7 +718,7 @@
      * @param vlanId VLAN ID
      * @return next objective ID or -1 if it was not found
      */
-    public int getVlanNextObjectiveId(DeviceId deviceId, VlanId vlanId) {
+    int getVlanNextObjectiveId(DeviceId deviceId, VlanId vlanId) {
         if (groupHandlerMap.get(deviceId) != null) {
             log.trace("getVlanNextObjectiveId query in device {}", deviceId);
             return groupHandlerMap.get(deviceId).getVlanNextObjectiveId(vlanId);
@@ -738,7 +762,7 @@
      * @param devId the device identifier
      * @return the groupHandler object for the device id, or null if not found
      */
-    public DefaultGroupHandler getGroupHandler(DeviceId devId) {
+    DefaultGroupHandler getGroupHandler(DeviceId devId) {
         return groupHandlerMap.get(devId);
     }
 
@@ -752,7 +776,7 @@
      * @param link the infrastructure link being queried
      * @return true if this controller instance has seen this link before
      */
-    public boolean isSeenLink(Link link) {
+    boolean isSeenLink(Link link) {
         return seenLinks.containsKey(link);
     }
 
@@ -763,7 +787,7 @@
      * @param link the link to update in the seen-link local store
      * @param up the status of the link, true if up, false if down
      */
-    public void updateSeenLink(Link link, boolean up) {
+    void updateSeenLink(Link link, boolean up) {
         seenLinks.put(link, up);
     }
 
@@ -776,7 +800,7 @@
      *         true if the seen-link is up;
      *         false if the seen-link is down
      */
-    public Boolean isSeenLinkUp(Link link) {
+    Boolean isSeenLinkUp(Link link) {
         return seenLinks.get(link);
     }
 
@@ -785,7 +809,7 @@
      *
      * @param link the infrastructure link to purge
      */
-    public void purgeSeenLink(Link link) {
+    private void purgeSeenLink(Link link) {
         seenLinks.remove(link);
     }
 
@@ -799,7 +823,7 @@
      *  @return true if a seen-link exists that is up, and shares the
      *          same src and dst switches as the link being queried
      */
-    public boolean isParallelLink(Link link) {
+    private boolean isParallelLink(Link link) {
         for (Entry<Link, Boolean> seen : seenLinks.entrySet()) {
             Link seenLink = seen.getKey();
            if (seenLink.equals(link)) {
@@ -959,7 +983,7 @@
 
     @SuppressWarnings("rawtypes")
     private void scheduleEventHandlerIfNotScheduled(Event event) {
-        synchronized (threadSchedulerLock) {
+        synchronized (THREAD_SCHED_LOCK) {
             eventQueue.add(event);
             numOfEventsQueued++;
 
@@ -981,8 +1005,8 @@
             try {
                 while (true) {
                     @SuppressWarnings("rawtypes")
-                    Event event = null;
-                    synchronized (threadSchedulerLock) {
+                    Event event;
+                    synchronized (THREAD_SCHED_LOCK) {
                         if (!eventQueue.isEmpty()) {
                             event = eventQueue.poll();
                             numOfEventsExecuted++;
@@ -1223,14 +1247,10 @@
                 });
         vlanNextObjStore.entrySet().stream()
                 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
-                .forEach(entry -> {
-                    vlanNextObjStore.remove(entry.getKey());
-                });
+                .forEach(entry -> vlanNextObjStore.remove(entry.getKey()));
         portNextObjStore.entrySet().stream()
                 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
-                .forEach(entry -> {
-                    portNextObjStore.remove(entry.getKey());
-                });
+                .forEach(entry -> portNextObjStore.remove(entry.getKey()));
 
         seenLinks.keySet().removeIf(key -> key.src().deviceId().equals(device.id()) ||
                 key.dst().deviceId().equals(device.id()));
@@ -1339,7 +1359,7 @@
          *
          * @param srManager segment routing manager
          */
-        public InternalConfigListener(SegmentRoutingManager srManager) {
+        InternalConfigListener(SegmentRoutingManager srManager) {
             this.srManager = srManager;
         }
 
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
index 706c549..7959df6 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
@@ -82,7 +82,7 @@
                 isEdgeRouter() != null &&
                 adjacencySids() != null &&
                 // pairDeviceId and pairLocalPort must be both configured or both omitted
-                !(hasField(PAIR_DEVICE_ID) ^ hasField(PAIR_LOCAL_PORT)) &&
+                (hasField(PAIR_DEVICE_ID) == hasField(PAIR_LOCAL_PORT)) &&
                 (!hasField(PAIR_DEVICE_ID) || pairDeviceId() != null) &&
                 (!hasField(PAIR_LOCAL_PORT) || pairLocalPort() != null);
     }
diff --git a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
index d9900c2..c0c7a04 100644
--- a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
+++ b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
@@ -16,6 +16,8 @@
 
 package org.onosproject.segmentrouting;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -35,6 +37,8 @@
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
 import org.onosproject.net.config.NetworkConfigRegistryAdapter;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
@@ -52,6 +56,7 @@
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
+import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
 
 import java.util.Map;
 import java.util.Objects;
@@ -90,10 +95,13 @@
     // Device
     private static final DeviceId DEV1 = DeviceId.deviceId("of:0000000000000001");
     private static final DeviceId DEV2 = DeviceId.deviceId("of:0000000000000002");
+    private static final DeviceId DEV3 = DeviceId.deviceId("of:000000000000003");
+    private static final DeviceId DEV4 = DeviceId.deviceId("of:000000000000004");
     // Port
     private static final PortNumber P1 = PortNumber.portNumber(1);
     private static final PortNumber P2 = PortNumber.portNumber(2);
     private static final PortNumber P3 = PortNumber.portNumber(3);
+    private static final PortNumber P9 = PortNumber.portNumber(9);
     // Connect Point
     private static final ConnectPoint CP11 = new ConnectPoint(DEV1, P1);
     private static final HostLocation HOST_LOC11 = new HostLocation(CP11, 0);
@@ -105,10 +113,19 @@
     private static final HostLocation HOST_LOC21 = new HostLocation(CP21, 0);
     private static final ConnectPoint CP22 = new ConnectPoint(DEV2, P2);
     private static final HostLocation HOST_LOC22 = new HostLocation(CP22, 0);
+    // Connect Point for dual-homed host failover
+    private static final ConnectPoint CP31 = new ConnectPoint(DEV3, P1);
+    private static final HostLocation HOST_LOC31 = new HostLocation(CP31, 0);
+    private static final ConnectPoint CP41 = new ConnectPoint(DEV4, P1);
+    private static final HostLocation HOST_LOC41 = new HostLocation(CP41, 0);
+    private static final ConnectPoint CP39 = new ConnectPoint(DEV3, P9);
+    private static final ConnectPoint CP49 = new ConnectPoint(DEV4, P9);
     // Interface VLAN
     private static final VlanId INTF_VLAN_UNTAGGED = VlanId.vlanId((short) 10);
     private static final Set<VlanId> INTF_VLAN_TAGGED = Sets.newHashSet(VlanId.vlanId((short) 20));
     private static final VlanId INTF_VLAN_NATIVE = VlanId.vlanId((short) 30);
+    private static final Set<VlanId> INTF_VLAN_PAIR = Sets.newHashSet(VlanId.vlanId((short) 10),
+            VlanId.vlanId((short) 20), VlanId.vlanId((short) 30));
     // Interface subnet
     private static final IpPrefix INTF_PREFIX1 = IpPrefix.valueOf("10.0.1.254/24");
     private static final IpPrefix INTF_PREFIX2 = IpPrefix.valueOf("10.0.2.254/24");
@@ -130,6 +147,7 @@
         srManager.routingRulePopulator = new MockRoutingRulePopulator();
         srManager.interfaceService = new MockInterfaceService();
         srManager.hostService = new MockHostService();
+        srManager.cfgService = new MockNetworkConfigRegistry();
 
         hostHandler = new HostHandler(srManager);
 
@@ -280,7 +298,7 @@
                 Sets.newHashSet(HOST_LOC13), Sets.newHashSet(HOST_IP11), false);
 
         // Add a host
-        // Expect: add a new routing rule. no change to bridging rule.
+        // Expect: add one new routing rule, one new bridging rule
         hostHandler.processHostAddedEvent(new HostEvent(HostEvent.Type.HOST_ADDED, host1));
         assertEquals(1, routingTable.size());
         assertNotNull(routingTable.get(new RoutingTableKey(DEV1, HOST_IP11.toIpPrefix())));
@@ -371,6 +389,88 @@
     }
 
     @Test
+    public void testDualHomingSingleLocationFail() throws Exception {
+        Host host1 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
+                Sets.newHashSet(HOST_LOC31, HOST_LOC41), Sets.newHashSet(HOST_IP11, HOST_IP12), false);
+        Host host2 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
+                Sets.newHashSet(HOST_LOC31), Sets.newHashSet(HOST_IP11, HOST_IP12), false);
+
+        // Add a host
+        // Expect: add four new routing rules, two new bridging rules
+        hostHandler.processHostAddedEvent(new HostEvent(HostEvent.Type.HOST_ADDED, host1));
+        assertEquals(4, routingTable.size());
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(2, bridgingTable.size());
+        assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+
+        // Host becomes single-homed
+        // Expect: redirect flows from host location to pair link
+        hostHandler.processHostMovedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host2, host1));
+        assertEquals(4, routingTable.size());
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(P9, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P9, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(2, bridgingTable.size());
+        assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        assertEquals(P9, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+
+        // Host becomes dual-homed again
+        // Expect: Redirect flows from pair link back to host location
+        hostHandler.processHostMovedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host1, host2));
+        assertEquals(4, routingTable.size());
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(2, bridgingTable.size());
+        assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+    }
+
+    @Test
+    public void testDualHomingBothLocationFail() throws Exception {
+        Host host1 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
+                Sets.newHashSet(HOST_LOC31, HOST_LOC41), Sets.newHashSet(HOST_IP11, HOST_IP12), false);
+        Host host2 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
+                Sets.newHashSet(HOST_LOC31), Sets.newHashSet(HOST_IP11, HOST_IP12), false);
+
+        // Add a host
+        // Expect: add four new routing rules, two new bridging rules
+        hostHandler.processHostAddedEvent(new HostEvent(HostEvent.Type.HOST_ADDED, host1));
+        assertEquals(4, routingTable.size());
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(2, bridgingTable.size());
+        assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+
+        // Host becomes single-homed
+        // Expect: redirect flows from host location to pair link
+        hostHandler.processHostMovedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host2, host1));
+        assertEquals(4, routingTable.size());
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, routingTable.get(new RoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(P9, routingTable.get(new RoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P9, routingTable.get(new RoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(2, bridgingTable.size());
+        assertEquals(P1, bridgingTable.get(new BridingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        assertEquals(P9, bridgingTable.get(new BridingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+
+        // Host loses both locations
+        // Expect: Remove last location and all previous redirection flows
+        hostHandler.processHostRemovedEvent(new HostEvent(HostEvent.Type.HOST_REMOVED, host2));
+        assertEquals(0, routingTable.size());
+        assertEquals(0, bridgingTable.size());
+    }
+
+    @Test
     public void testHostUpdated() throws Exception {
         Host host1 = new DefaultHost(PROVIDER_ID, HOST_ID_UNTAGGED, HOST_MAC, HOST_VLAN_UNTAGGED,
                 Sets.newHashSet(HOST_LOC11), Sets.newHashSet(HOST_IP11), false);
@@ -501,12 +601,51 @@
             } else if (CP22.equals(cp)) {
                 intf = new Interface(null, CP22, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
                         INTF_VLAN_UNTAGGED, null, null);
+            } else if (CP31.equals(cp)) {
+                intf = new Interface(null, CP31, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
+                        INTF_VLAN_UNTAGGED, null, null);
+            } else if (CP39.equals(cp)) {
+                intf = new Interface(null, CP39, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
+                        null, INTF_VLAN_PAIR, null);
+            } else if (CP41.equals(cp)) {
+                intf = new Interface(null, CP41, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
+                        INTF_VLAN_UNTAGGED, null, null);
+            } else if (CP49.equals(cp)) {
+                intf = new Interface(null, CP49, Lists.newArrayList(INTF_IP1), MacAddress.NONE, null,
+                        null, INTF_VLAN_PAIR, null);
             }
-
             return Objects.nonNull(intf) ? Sets.newHashSet(intf) : Sets.newHashSet();
         }
     }
 
+    class MockNetworkConfigRegistry extends NetworkConfigRegistryAdapter {
+        @Override
+        public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
+            if (subject instanceof DeviceId) {
+                DeviceId deviceId = (DeviceId) subject;
+                ObjectMapper mapper = new ObjectMapper();
+                ConfigApplyDelegate delegate = new MockCfgDelegate();
+                JsonNode emptyTree = new ObjectMapper().createObjectNode();
+
+                SegmentRoutingDeviceConfig config = new SegmentRoutingDeviceConfig();
+                config.init(deviceId, "host-handler-test", emptyTree, mapper, delegate);
+                config.setPairDeviceId(subject.equals(DEV3) ? DEV4 : DEV3)
+                        .setPairLocalPort(P9);
+
+                return (C) config;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    class MockCfgDelegate implements ConfigApplyDelegate {
+        @Override
+        public void onApply(@SuppressWarnings("rawtypes") Config config) {
+            config.apply();
+        }
+    }
+
     class MockFlowObjectiveService extends FlowObjectiveServiceAdapter {
         @Override
         public void forward(DeviceId deviceId, ForwardingObjective forwardingObjective) {