CORD-248 Provide host management network connectivity to a VM

Change-Id: I01732c9defe65de227a147c8ad9b63bc4ec18956
diff --git a/src/main/java/org/opencord/cordvtn/impl/CordVtnArpProxy.java b/src/main/java/org/opencord/cordvtn/impl/CordVtnArpProxy.java
index 9e8d313..1697f1a 100644
--- a/src/main/java/org/opencord/cordvtn/impl/CordVtnArpProxy.java
+++ b/src/main/java/org/opencord/cordvtn/impl/CordVtnArpProxy.java
@@ -28,6 +28,8 @@
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.packet.PacketProcessor;
 import org.onosproject.xosclient.api.VtnService;
 import org.opencord.cordvtn.api.CordVtnConfig;
@@ -50,6 +52,7 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onosproject.xosclient.api.VtnServiceApi.NetworkType.PRIVATE;
+import static org.onosproject.xosclient.api.VtnServiceApi.ServiceType.MANAGEMENT;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -62,6 +65,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected PacketService packetService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CordVtnNodeManager nodeManager;
+
     private final PacketProcessor packetProcessor = new InternalPacketProcessor();
     private final Map<Ip4Address, MacAddress> gateways = Maps.newConcurrentMap();
 
@@ -132,31 +138,26 @@
 
     /**
      * Emits ARP reply with fake MAC address for a given ARP request.
-     * It only handles requests for the registered service IPs, and the other
-     * requests can be handled by other ARP handlers like openstackSwitching or
-     * proxyArp, for example.
+     * It only handles requests for the registered gateway IPs and host IPs.
      *
      * @param context packet context
      * @param ethPacket ethernet packet
      */
-    private void processArpPacket(PacketContext context, Ethernet ethPacket) {
+    private void processArpRequest(PacketContext context, Ethernet ethPacket) {
         ARP arpPacket = (ARP) ethPacket.getPayload();
-        if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
-           return;
-        }
-
         Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
 
         MacAddress gatewayMac = gateways.get(targetIp);
-        MacAddress replyMac = gatewayMac != null ? gatewayMac : getMacFromHostService(targetIp);
+        MacAddress replyMac = gatewayMac != null ? gatewayMac :
+                getMacFromHostService(targetIp);
 
         if (replyMac.equals(MacAddress.NONE)) {
-            log.debug("Failed to find MAC for {}", targetIp.toString());
-            context.block();
+            log.trace("Failed to find MAC for {}", targetIp);
+            forwardManagementArpRequest(context, ethPacket);
             return;
         }
 
-        log.trace("Send ARP reply for {} with {}", targetIp.toString(), replyMac.toString());
+        log.trace("Send ARP reply for {} with {}", targetIp, replyMac);
         Ethernet ethReply = ARP.buildArpReply(
                 targetIp,
                 replyMac,
@@ -174,6 +175,62 @@
         context.block();
     }
 
+    private void processArpReply(PacketContext context, Ethernet ethPacket) {
+        ARP arpPacket = (ARP) ethPacket.getPayload();
+        Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
+
+        DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
+        Host host = hostService.getHostsByIp(targetIp).stream()
+                .filter(h -> h.location().deviceId().equals(deviceId))
+                .findFirst()
+                .orElse(null);
+
+        if (host == null) {
+            // do nothing for the unknown ARP reply
+            log.trace("No host found for {} in {}", targetIp, deviceId);
+            context.block();
+            return;
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(host.location().port())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                deviceId,
+                treatment,
+                ByteBuffer.wrap(ethPacket.serialize())));
+
+        context.block();
+    }
+
+    private void forwardManagementArpRequest(PacketContext context, Ethernet ethPacket) {
+        DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
+        PortNumber hostMgmtPort = nodeManager.hostManagementPort(deviceId);
+        Host host = hostService.getConnectedHosts(context.inPacket().receivedFrom())
+                .stream()
+                .findFirst().orElse(null);
+
+        if (host == null ||
+                !Instance.of(host).serviceType().equals(MANAGEMENT) ||
+                hostMgmtPort == null) {
+            context.block();
+            return;
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(hostMgmtPort)
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                context.inPacket().receivedFrom().deviceId(),
+                treatment,
+                ByteBuffer.wrap(ethPacket.serialize())));
+
+        log.trace("Forward ARP request to management network");
+        context.block();
+    }
+
     /**
      * Emits gratuitous ARP when a gateway mac address has been changed.
      *
@@ -183,7 +240,7 @@
     private void sendGratuitousArp(IpAddress gatewayIp, Set<Instance> instances) {
         MacAddress gatewayMac = gateways.get(gatewayIp.getIp4Address());
         if (gatewayMac == null) {
-            log.debug("Gateway {} is not registered to ARP proxy", gatewayIp.toString());
+            log.debug("Gateway {} is not registered to ARP proxy", gatewayIp);
             return;
         }
 
@@ -246,7 +303,7 @@
                 .orElse(null);
 
         if (host != null) {
-            log.trace("Found MAC from host service for {}", targetIp.toString());
+            log.trace("Found MAC from host service for {}", targetIp);
             return host.mac();
         } else {
             return MacAddress.NONE;
@@ -264,7 +321,18 @@
             if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
                 return;
             }
-            processArpPacket(context, ethPacket);
+
+            ARP arpPacket = (ARP) ethPacket.getPayload();
+            switch (arpPacket.getOpCode()) {
+                case ARP.OP_REQUEST:
+                    processArpRequest(context, ethPacket);
+                    break;
+                case ARP.OP_REPLY:
+                    processArpReply(context, ethPacket);
+                    break;
+                default:
+                    break;
+            }
         }
     }
 
diff --git a/src/main/java/org/opencord/cordvtn/impl/CordVtnNodeManager.java b/src/main/java/org/opencord/cordvtn/impl/CordVtnNodeManager.java
index 463c5d5..1926f03 100644
--- a/src/main/java/org/opencord/cordvtn/impl/CordVtnNodeManager.java
+++ b/src/main/java/org/opencord/cordvtn/impl/CordVtnNodeManager.java
@@ -28,6 +28,7 @@
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.LeadershipService;
 import org.onosproject.cluster.NodeId;
+import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.behaviour.BridgeDescription;
 import org.onosproject.net.behaviour.DefaultBridgeDescription;
 import org.onosproject.net.behaviour.InterfaceConfig;
@@ -75,6 +76,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
@@ -316,12 +318,9 @@
             log.warn("Failed to get node for {}", deviceId);
             return null;
         }
-        Port port = deviceService.getPorts(deviceId).stream()
-                .filter(p -> portName(p).contains(node.dataIface()) &&
-                        p.isEnabled())
-                .findFirst().orElse(null);
 
-        return port == null ? null : port.number();
+        Optional<PortNumber> port = getPortNumber(deviceId, node.dataIface());
+        return port.isPresent() ? port.get() : null;
     }
 
     /**
@@ -346,11 +345,39 @@
      * @return port number
      */
     public PortNumber tunnelPort(DeviceId deviceId) {
-        Port port = deviceService.getPorts(deviceId).stream()
-                .filter(p -> portName(p).contains(DEFAULT_TUNNEL))
-                .findFirst().orElse(null);
+        Optional<PortNumber> port = getPortNumber(deviceId, DEFAULT_TUNNEL);
+        return port.isPresent() ? port.get() : null;
+    }
 
-        return port == null ? null : port.number();
+    /**
+     * Returns host management interface port number if exists.
+     *
+     * @param deviceId integration bridge device id
+     * @return port number; null if it does not exist
+     */
+    public PortNumber hostManagementPort(DeviceId deviceId) {
+        CordVtnNode node = nodeByBridgeId(deviceId);
+        if (node == null) {
+            log.warn("Failed to get node for {}", deviceId);
+            return null;
+        }
+
+        if (node.hostMgmtIface().isPresent()) {
+            Optional<PortNumber> port = getPortNumber(deviceId, node.hostMgmtIface().get());
+            return port.isPresent() ? port.get() : null;
+        } else {
+            return null;
+        }
+    }
+
+    private Optional<PortNumber> getPortNumber(DeviceId deviceId, String portName) {
+        PortNumber port = deviceService.getPorts(deviceId).stream()
+                .filter(p -> p.annotations().value(AnnotationKeys.PORT_NAME).equals(portName) &&
+                        p.isEnabled())
+                .map(Port::number)
+                .findAny()
+                .orElse(null);
+        return Optional.ofNullable(port);
     }
 
     /**
@@ -400,8 +427,7 @@
             ovsdbClient.disconnect();
         }
 
-        pipeline.initPipeline(node, dataPort(node.integrationBridgeId()),
-                              tunnelPort(node.integrationBridgeId()));
+        pipeline.initPipeline(node);
 
         // adds existing instances to the host list
         deviceService.getPorts(node.integrationBridgeId()).stream()
diff --git a/src/main/java/org/opencord/cordvtn/impl/CordVtnPipeline.java b/src/main/java/org/opencord/cordvtn/impl/CordVtnPipeline.java
index 2aa9f1f..4e01d2b 100644
--- a/src/main/java/org/opencord/cordvtn/impl/CordVtnPipeline.java
+++ b/src/main/java/org/opencord/cordvtn/impl/CordVtnPipeline.java
@@ -27,7 +27,8 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.TpPort;
 import org.onlab.packet.VlanId;
-import org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Port;
 import org.opencord.cordvtn.api.Constants;
 import org.opencord.cordvtn.api.CordVtnNode;
 import org.onosproject.core.ApplicationId;
@@ -50,8 +51,11 @@
 import org.onosproject.net.flow.instructions.ExtensionTreatment;
 import org.slf4j.Logger;
 
+import java.util.Optional;
+
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
+import static org.opencord.cordvtn.api.Constants.DEFAULT_TUNNEL;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -91,6 +95,8 @@
     public static final int VXLAN_UDP_PORT = 4789;
     public static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
 
+    public static final String PROPERTY_TUNNEL_DST = "tunnelDst";
+
     private ApplicationId appId;
 
     @Activate
@@ -115,19 +121,84 @@
      * Installs table miss rule to a give device.
      *
      * @param node cordvtn node
-     * @param dataPort data plane port number
-     * @param tunnelPort tunnel port number
      */
-    public void initPipeline(CordVtnNode node, PortNumber dataPort, PortNumber tunnelPort) {
+    public void initPipeline(CordVtnNode node) {
         checkNotNull(node);
 
-        processTableZero(node.integrationBridgeId(), dataPort, node.dataIp().ip());
-        processInPortTable(node.integrationBridgeId(), tunnelPort, dataPort);
-        processAccessTypeTable(node.integrationBridgeId(), dataPort);
-        processVlanTable(node.integrationBridgeId(), dataPort);
+        Optional<PortNumber> dataPort = getPortNumber(node.integrationBridgeId(), node.dataIface());
+        Optional<PortNumber> tunnelPort = getPortNumber(node.integrationBridgeId(), DEFAULT_TUNNEL);
+        if (!dataPort.isPresent() || !tunnelPort.isPresent()) {
+            log.warn("Node is not in COMPLETE state");
+            return;
+        }
+
+        Optional<PortNumber> hostMgmtPort = Optional.empty();
+        if (node.hostMgmtIface().isPresent()) {
+            hostMgmtPort = getPortNumber(node.integrationBridgeId(), node.hostMgmtIface().get());
+        }
+
+        processTableZero(node.integrationBridgeId(),
+                         dataPort.get(),
+                         node.dataIp().ip(),
+                         node.localMgmtIp().ip());
+
+        processInPortTable(node.integrationBridgeId(),
+                           tunnelPort.get(),
+                           dataPort.get(),
+                           hostMgmtPort);
+
+        processAccessTypeTable(node.integrationBridgeId(), dataPort.get());
+        processVlanTable(node.integrationBridgeId(), dataPort.get());
     }
 
-    private void processTableZero(DeviceId deviceId, PortNumber dataPort, IpAddress dataIp) {
+    private void processTableZero(DeviceId deviceId, PortNumber dataPort, IpAddress dataIp,
+                                  IpAddress localMgmtIp) {
+        vxlanShuttleRule(deviceId, dataPort, dataIp);
+        localManagementBaseRule(deviceId, localMgmtIp.getIp4Address());
+
+        // take all vlan tagged packet to the VLAN table
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VlanId.ANY)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_VLAN)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_MANAGEMENT)
+                .forDevice(deviceId)
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        // take all other packets to the next table
+        selector = DefaultTrafficSelector.builder()
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_IN_PORT)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(PRIORITY_ZERO)
+                .forDevice(deviceId)
+                .forTable(TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+    }
+
+    private void vxlanShuttleRule(DeviceId deviceId, PortNumber dataPort, IpAddress dataIp) {
         // take vxlan packet out onto the physical port
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchInPort(PortNumber.LOCAL)
@@ -218,52 +289,98 @@
                 .build();
 
         processFlowRule(true, flowRule);
+    }
 
-        // take all else to the next table
-        selector = DefaultTrafficSelector.builder()
+    private void localManagementBaseRule(DeviceId deviceId, Ip4Address localMgmtIp) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_ARP)
+                .matchArpTpa(localMgmtIp)
                 .build();
 
-        treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_IN_PORT)
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.LOCAL)
                 .build();
 
-        flowRule = DefaultFlowRule.builder()
+        FlowRule flowRule = DefaultFlowRule.builder()
                 .fromApp(appId)
                 .withSelector(selector)
                 .withTreatment(treatment)
-                .withPriority(PRIORITY_ZERO)
+                .withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
                 .forDevice(deviceId)
-                .forTable(TABLE_ZERO)
+                .forTable(CordVtnPipeline.TABLE_ZERO)
                 .makePermanent()
                 .build();
 
         processFlowRule(true, flowRule);
 
-        // take all vlan tagged packet to the VLAN table
         selector = DefaultTrafficSelector.builder()
-                .matchVlanId(VlanId.ANY)
+                .matchInPort(PortNumber.LOCAL)
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(localMgmtIp.toIpPrefix())
                 .build();
 
         treatment = DefaultTrafficTreatment.builder()
-                .transition(TABLE_VLAN)
+                .transition(CordVtnPipeline.TABLE_DST_IP)
                 .build();
 
         flowRule = DefaultFlowRule.builder()
                 .fromApp(appId)
                 .withSelector(selector)
                 .withTreatment(treatment)
-                .withPriority(PRIORITY_MANAGEMENT)
+                .withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
                 .forDevice(deviceId)
-                .forTable(TABLE_ZERO)
+                .forTable(CordVtnPipeline.TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(localMgmtIp.toIpPrefix())
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.LOCAL)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
+                .forDevice(deviceId)
+                .forTable(CordVtnPipeline.TABLE_ZERO)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(PortNumber.LOCAL)
+                .matchEthType(Ethernet.TYPE_ARP)
+                .matchArpSpa(localMgmtIp)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.CONTROLLER)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
+                .forDevice(deviceId)
+                .forTable(CordVtnPipeline.TABLE_ZERO)
                 .makePermanent()
                 .build();
 
         processFlowRule(true, flowRule);
     }
 
-    private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dataPort) {
-        checkNotNull(tunnelPort);
-
+    private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dataPort,
+                                    Optional<PortNumber> hostMgmtPort) {
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchInPort(tunnelPort)
                 .build();
@@ -303,6 +420,28 @@
                 .build();
 
         processFlowRule(true, flowRule);
+
+        if (hostMgmtPort.isPresent()) {
+            selector = DefaultTrafficSelector.builder()
+                    .matchInPort(hostMgmtPort.get())
+                    .build();
+
+            treatment = DefaultTrafficTreatment.builder()
+                    .transition(TABLE_DST_IP)
+                    .build();
+
+            flowRule = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(selector)
+                    .withTreatment(treatment)
+                    .withPriority(PRIORITY_DEFAULT)
+                    .forDevice(deviceId)
+                    .forTable(TABLE_IN_PORT)
+                    .makePermanent()
+                    .build();
+
+            processFlowRule(true, flowRule);
+        }
     }
 
     private void processAccessTypeTable(DeviceId deviceId, PortNumber dataPort) {
@@ -384,23 +523,30 @@
     }
 
     public ExtensionTreatment tunnelDstTreatment(DeviceId deviceId, Ip4Address remoteIp) {
-        try {
-            Device device = deviceService.getDevice(deviceId);
-            if (!device.is(ExtensionTreatmentResolver.class)) {
-                log.error("The extension treatment is not supported");
-                return null;
-
-            }
-
-            ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
-            ExtensionTreatment treatment =
-                    resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
-            treatment.setPropertyValue("tunnelDst", remoteIp);
-            return treatment;
-        } catch (ItemNotFoundException | UnsupportedOperationException |
-                ExtensionPropertyException e) {
-            log.error("Failed to get extension instruction {}", deviceId);
+        Device device = deviceService.getDevice(deviceId);
+        if (device != null && !device.is(ExtensionTreatmentResolver.class)) {
+            log.error("The extension treatment is not supported");
             return null;
         }
+
+        ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
+        ExtensionTreatment treatment = resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
+        try {
+            treatment.setPropertyValue(PROPERTY_TUNNEL_DST, remoteIp);
+            return treatment;
+        } catch (ExtensionPropertyException e) {
+            log.warn("Failed to get tunnelDst extension treatment for {}", deviceId);
+            return null;
+        }
+    }
+
+    private Optional<PortNumber> getPortNumber(DeviceId deviceId, String portName) {
+        PortNumber port = deviceService.getPorts(deviceId).stream()
+                .filter(p -> p.annotations().value(AnnotationKeys.PORT_NAME).equals(portName) &&
+                        p.isEnabled())
+                .map(Port::number)
+                .findAny()
+                .orElse(null);
+        return Optional.ofNullable(port);
     }
 }
diff --git a/src/main/java/org/opencord/cordvtn/impl/handler/ManagementInstanceHandler.java b/src/main/java/org/opencord/cordvtn/impl/handler/ManagementInstanceHandler.java
index fe30781..f369df1 100644
--- a/src/main/java/org/opencord/cordvtn/impl/handler/ManagementInstanceHandler.java
+++ b/src/main/java/org/opencord/cordvtn/impl/handler/ManagementInstanceHandler.java
@@ -32,6 +32,7 @@
 import org.opencord.cordvtn.impl.AbstractInstanceHandler;
 import org.opencord.cordvtn.api.Instance;
 import org.opencord.cordvtn.api.InstanceHandler;
+import org.opencord.cordvtn.impl.CordVtnNodeManager;
 import org.opencord.cordvtn.impl.CordVtnPipeline;
 
 import java.util.Optional;
@@ -47,6 +48,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CordVtnPipeline pipeline;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CordVtnNodeManager nodeManager;
+
     @Activate
     protected void activate() {
         serviceType = Optional.of(MANAGEMENT);
@@ -60,116 +64,56 @@
 
     @Override
     public void instanceDetected(Instance instance) {
-        log.info("Instance is detected {}", instance);
-
         VtnService service = getVtnService(instance.serviceId());
         if (service == null) {
             log.warn("Failed to get VtnService for {}", instance);
             return;
         }
-        localMgmtNetworkRules(instance, service, true);
+
+        switch (service.networkType()) {
+            case MANAGEMENT_LOCAL:
+                log.info("LOCAL management instance is detected {}", instance);
+                localManagementRules(instance, true);
+                break;
+            case MANAGEMENT_HOSTS:
+                log.info("HOSTS management instance is detected {}", instance);
+                hostsManagementRules(instance, true);
+                break;
+            default:
+                break;
+        }
     }
 
     @Override
     public void instanceRemoved(Instance instance) {
-        log.info("Instance is removed {}", instance);
-
         VtnService service = getVtnService(instance.serviceId());
         if (service == null) {
             log.warn("Failed to get VtnService for {}", instance);
             return;
         }
 
-        // TODO check if any stale management network rules are
-        localMgmtNetworkRules(instance, service, false);
-    }
-
-    private void localMgmtNetworkRules(Instance instance, VtnService service, boolean install) {
-        managementPerInstanceRule(instance, install);
-        if (install) {
-            managementBaseRule(instance, service, true);
-        } else if (!hostService.getConnectedHosts(instance.deviceId()).stream()
-                .filter(host -> Instance.of(host).serviceId().equals(service.id()))
-                .findAny()
-                .isPresent()) {
-            managementBaseRule(instance, service, false);
+        switch (service.networkType()) {
+            case MANAGEMENT_LOCAL:
+                log.info("LOCAL management instance removed {}", instance);
+                localManagementRules(instance, false);
+                break;
+            case MANAGEMENT_HOSTS:
+                log.info("HOSTS management instance removed {}", instance);
+                hostsManagementRules(instance, false);
+                break;
+            default:
+                break;
         }
     }
 
-    private void managementBaseRule(Instance instance, VtnService service, boolean install) {
+    private void localManagementRules(Instance instance, boolean install) {
         TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_ARP)
-                .matchArpTpa(service.serviceIp().getIp4Address())
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(instance.ipAddress().toIpPrefix())
                 .build();
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.LOCAL)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
-                .forDevice(instance.deviceId())
-                .forTable(CordVtnPipeline.TABLE_ZERO)
-                .makePermanent()
-                .build();
-
-        pipeline.processFlowRule(install, flowRule);
-
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(PortNumber.LOCAL)
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(service.subnet())
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .transition(CordVtnPipeline.TABLE_DST_IP)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
-                .forDevice(instance.deviceId())
-                .forTable(CordVtnPipeline.TABLE_ZERO)
-                .makePermanent()
-                .build();
-
-        pipeline.processFlowRule(install, flowRule);
-
-        selector = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPDst(service.serviceIp().toIpPrefix())
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.LOCAL)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
-                .forDevice(instance.deviceId())
-                .forTable(CordVtnPipeline.TABLE_ZERO)
-                .makePermanent()
-                .build();
-
-        pipeline.processFlowRule(install, flowRule);
-    }
-
-    private void managementPerInstanceRule(Instance instance, boolean install) {
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchInPort(PortNumber.LOCAL)
-                .matchEthType(Ethernet.TYPE_ARP)
-                .matchArpTpa(instance.ipAddress().getIp4Address())
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(instance.mac())
                 .setOutput(instance.portNumber())
                 .build();
 
@@ -177,9 +121,37 @@
                 .fromApp(appId)
                 .withSelector(selector)
                 .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
+                .forDevice(instance.deviceId())
+                .forTable(CordVtnPipeline.TABLE_DST_IP)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+
+    private void hostsManagementRules(Instance instance, boolean install) {
+        PortNumber hostMgmtPort = nodeManager.hostManagementPort(instance.deviceId());
+        if (hostMgmtPort == null) {
+            log.warn("Can not find host management port in {}", instance.deviceId());
+            return;
+        }
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(instance.portNumber())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(hostMgmtPort)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
                 .withPriority(CordVtnPipeline.PRIORITY_MANAGEMENT)
                 .forDevice(instance.deviceId())
-                .forTable(CordVtnPipeline.TABLE_ZERO)
+                .forTable(CordVtnPipeline.TABLE_IN_PORT)
                 .makePermanent()
                 .build();