ONOS Network Troubleshooting System

Newest Commit changes:

1. Add unit tests.
2. Fix review comments.
3. Add support to BUCK.

Could you please make a Code Review, we wish to hear anything from you :)

Thank you very much!

----------------------------------------------------

ONOS Network Troubleshooting System

Modularity design. In present, include these tow module:
1. Routing Loop Detection

Welcome your contribution for more modules in the future...

Beijing University of Posts and Telecommunications

new: withdraw blackhole tracing for redesign;
     fix obvious checkstyle problem.

Change-Id: Id6d3aa0bc00c8da8ac046e6903f17cfdf954d919
diff --git a/apps/network-troubleshoot/core/BUCK b/apps/network-troubleshoot/core/BUCK
new file mode 100644
index 0000000..1174519
--- /dev/null
+++ b/apps/network-troubleshoot/core/BUCK
@@ -0,0 +1,17 @@
+COMPILE_DEPS = [
+  '//lib:CORE_DEPS',
+  '//incubator/api:onos-incubator-api',
+#   '//core/store/serializers:onos-core-serializers',
+  '//apps/network-troubleshoot/api:onos-apps-network-troubleshoot-api',
+]
+
+TEST_DEPS = [
+  '//lib:TEST_ADAPTERS',
+  '//utils/osgi:onlab-osgi-tests',
+  '//incubator/api:onos-incubator-api-tests',
+]
+
+osgi_jar_with_tests (
+  deps = COMPILE_DEPS,
+  test_deps = TEST_DEPS,
+)
diff --git a/apps/network-troubleshoot/core/pom.xml b/apps/network-troubleshoot/core/pom.xml
new file mode 100644
index 0000000..43f0281
--- /dev/null
+++ b/apps/network-troubleshoot/core/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2015-present Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-network-troubleshoot</artifactId>
+        <version>1.10.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>onos-network-troubleshoot-core</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>ONOS Network TroubleShoot SubSystem</description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-network-troubleshoot-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- for component config -->
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+
+        <!-- Test -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/DefaultCheckLoop.java b/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/DefaultCheckLoop.java
new file mode 100644
index 0000000..3634683
--- /dev/null
+++ b/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/DefaultCheckLoop.java
@@ -0,0 +1,617 @@
+/*
+ * Copyright 2015-present 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.fnl.impl;
+
+import org.onlab.packet.IpPrefix;
+import org.onosproject.fnl.intf.NetworkAnomaly;
+import org.onosproject.fnl.intf.NetworkDiagnostic;
+import org.onosproject.fnl.base.TsLoopPacket;
+import org.onosproject.fnl.base.TsReturn;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.criteria.IPProtocolCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.link.LinkService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.packet.EthType.EtherType.IPV4;
+import static org.onlab.packet.EthType.EtherType.VLAN;
+import static org.onosproject.fnl.base.NetworkDiagnosticUtils.isDevice;
+import static org.onosproject.fnl.base.NetworkDiagnosticUtils.sortCriteria;
+import static org.onosproject.fnl.base.NetworkDiagnosticUtils.sortFlowTable;
+import static org.onosproject.fnl.base.TsLoopPacket.matchBuilder;
+import static org.onosproject.fnl.intf.NetworkDiagnostic.Type.LOOP;
+import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
+import static org.onosproject.net.flow.criteria.Criteria.matchInPort;
+import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_TYPE;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IN_PORT;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IP_PROTO;
+
+/**
+ * Loop Checking Diagnostic Implementation.
+ *
+ * Strategy Pattern.
+ */
+public class DefaultCheckLoop implements NetworkDiagnostic {
+
+    private static final int IP_PROTO_TCP_TS = 6;
+    private static final int IP_PROTO_UDP_TS = 17;
+
+    private static final String E_CANNOT_HANDLE = "can not handle {} now.";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final DeviceService deviceService;
+    private final HostService hostService;
+    private final FlowRuleService flowRuleService;
+    private final LinkService linkService;
+
+
+    private Map<DeviceId, Device> deviceInfo;
+    private Map<DeviceId, Iterable<FlowEntry>> flowEntryInfo;
+    private Set<Device> accessDevices;
+
+    //conventionally used by tsGetEgressLinks()
+    private Map<DeviceId, Set<Link>> egressLinkInfo;
+
+    //Two below are hot data in checking process.
+    private Set<NetworkAnomaly> loops;
+    private Set<DeviceId> excludeDeviceId;
+
+    /**
+     * Creates and returns an instance of Loop Checking algorithm module.
+     *
+     * @param ds reference of DeviceService
+     * @param hs reference of HostService
+     * @param frs reference of FlowRuleService
+     * @param ls reference of LinkService
+     */
+    public DefaultCheckLoop(DeviceService ds,
+                            HostService hs,
+                            FlowRuleService frs,
+                            LinkService ls) {
+        checkNotNull(ds, "DeviceService cannot be null");
+        checkNotNull(hs, "HostService cannot be null");
+        checkNotNull(frs, "FlowRuleService cannot be null");
+        checkNotNull(ls, "LinkService  cannot be null");
+
+        deviceService = ds;
+        hostService = hs;
+        flowRuleService = frs;
+        linkService = ls;
+    }
+
+    /**
+     * Checks for loops and returns any that were found.
+     * An empty set is returned if there are no loops.
+     *
+     * @return the set of loops; may be empty
+     */
+    @Override
+    public Set<NetworkAnomaly> findAnomalies() {
+        return findLoop();
+    }
+
+    @Override
+    public Type type() {
+        return LOOP;
+    }
+
+    /**
+     * Enter of the loop checking algorithm.
+     *
+     * @return the set of loop results; empty, if there is no loop
+     */
+    private Set<NetworkAnomaly> findLoop() {
+
+        getNetworkSnapshot();
+
+        loops = new HashSet<>();
+        excludeDeviceId = new HashSet<>();
+
+        for (Device device : accessDevices) {
+            if (excludeDeviceId.contains(device.id())) {
+                continue;
+            }
+
+
+            List<FlowEntry> availableFlowEntries = new ArrayList<>();
+
+            flowEntryInfo.get(device.id()).forEach(flowEntry -> {
+                if (flowEntry.state() == ADDED) {
+                    availableFlowEntries.add(flowEntry);
+                }
+            });
+
+            List<FlowEntry> sortedFlowEntries = sortFlowTable(availableFlowEntries);
+
+
+            for (FlowEntry flow : sortedFlowEntries) {
+
+                TsLoopPacket pkt =
+                        matchBuilder(flow.selector().criteria(), null);
+
+                pkt.pushPathFlow(flow);
+
+                List<Instruction> inst = flow.treatment().immediate();
+
+                for (Instruction instOne : inst) {
+                    // Attention !!!
+                    // if you would like to modify the code here,
+                    // please MAKE VERY SURE that you are clear with
+                    // the relationship of any invoked methods, and
+                    // the relationship of params which are passed in and out.
+                    processOneInstruction(instOne, device.id(),
+                            null, pkt, true, flow);
+                }
+            }
+        }
+
+        // TODO - avoid two-hop LOOP
+
+        // TODO - another clean operations
+
+        return loops;
+    }
+
+    /**
+     * Iterate one by one at switch hops.
+     * Return whether we discover a Loop now or not.
+     *
+     * When flows form a loop,
+     * pkt is also a return value indicating the loop header.
+     *
+     * @param deviceId the device needed to be checked
+     * @param pkt virtual packet forwarded by switches
+     * @return true if a loop is discovered
+     */
+    private boolean matchDeviceFlows(DeviceId deviceId, TsLoopPacket pkt) {
+        if (pkt.isPassedDevice(deviceId)) {
+            return true; // Attention: pkt should be held outside
+        }
+
+
+        List<FlowEntry> availableFlowEntries = new ArrayList<>();
+
+        flowEntryInfo.get(deviceId).forEach(flowEntry -> {
+            if (flowEntry.state() == ADDED) {
+                availableFlowEntries.add(flowEntry);
+            }
+        });
+
+        List<FlowEntry> sortedFlowEntries = sortFlowTable(availableFlowEntries);
+
+
+        for (FlowEntry flowEntry : sortedFlowEntries) {
+            TsReturn<Boolean> isBigger = new TsReturn<>();
+            TsLoopPacket newPkt = pkt.copyPacketMatch();
+
+            if (!matchAndAddFlowEntry(flowEntry, newPkt, isBigger)) {
+                continue;
+            }
+
+            newPkt.pushPathFlow(flowEntry);
+            // no need to popPathFlow(),
+            // because we will drop this newPkt, and copy pkt again
+
+            for (Instruction instOne : flowEntry.treatment().immediate()) {
+                // Attention !!!
+                // if you would like to modify the code here,
+                // please MAKE VERY SURE that you are clear with
+                // the relationship of any invoked methods, and
+                // the relationship of params which are passed in and out.
+                if (processOneInstruction(instOne, deviceId,
+                        pkt, newPkt, false, null)) {
+                    return true;
+                }
+            }
+
+            newPkt.popPathFlow();
+
+            if (!isBigger.getValue()) {
+                break;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Process one of every instructions.
+     *
+     * The isFindLoop should be true if it is invoked by findLoop method,
+     * and be false if it is invoked by matchDeviceFlows method.
+     *
+     * @param instOne the instruction to be processed
+     * @param currentDeviceId id of the device we are now in
+     * @param initPkt the packet before being copied
+     * @param matchedPkt the packet which matched the flow entry,
+     *                   to which this instruction belongs
+     * @param isFindLoop indicate if it is invoked by findLoop method
+     * @param firstEntry the flow entry from which the packet is generated
+     * @return true, if a loop is discovered;
+     *         false, 1. invoked by matchDeviceFlows method, and detected no loop;
+     *                2. invoked by findLoop method
+     */
+    private boolean processOneInstruction(Instruction instOne,
+                                          DeviceId currentDeviceId,
+                                          TsLoopPacket initPkt,
+                                          TsLoopPacket matchedPkt,
+                                          boolean isFindLoop,
+                                          FlowEntry firstEntry) {
+        if (isFindLoop) {
+            checkArgument(initPkt == null,
+                    "initPkt is not null, while isFindLoop is true.");
+        } else {
+            checkArgument(firstEntry == null,
+                    "firstEntry is not null, while isFindLoop is false.");
+        }
+
+
+        Instruction.Type type = instOne.type();
+        switch (type) {
+            case L2MODIFICATION:
+                //TODO - modify the L2 header of virtual packet
+                log.warn(E_CANNOT_HANDLE, type);
+                break;
+            case L3MODIFICATION:
+                //TODO - modify the L3 header of virtual packet
+                log.warn(E_CANNOT_HANDLE, type);
+                break;
+            case L4MODIFICATION:
+                //TODO - modify the L4 header of virtual packet
+                log.warn(E_CANNOT_HANDLE, type);
+                break;
+            case OUTPUT:
+                if (processOneOutputInstruction(instOne, currentDeviceId,
+                        initPkt, matchedPkt, isFindLoop, firstEntry)) {
+                    return true;
+                }
+                break;
+            default:
+                log.error("Can't process this type of instruction: {} now.",
+                        instOne.type());
+                break;
+        }
+        return false;
+    }
+
+    /**
+     * Process one output instruction.
+     *
+     * Params are passed from processOneInstruction directly,
+     * and obey the same rules.
+     *
+     * @param instOne the instruction to be processed
+     * @param currentDeviceId id of the device we are now in
+     * @param initPkt the packet before being copied
+     * @param matchedPkt the packet which matched the flow entry,
+     *                   to which this instruction belongs
+     * @param isFindLoop indicate if it is invoked by findLoop method
+     * @param firstEntry the flow entry from which the packet is generated
+     * @return true, if a loop is discovered;
+     *         false, 1. invoked by matchDeviceFlows method, and detected no loop;
+     *                2. invoked by findLoop method
+     */
+    private boolean processOneOutputInstruction(Instruction instOne,
+                                                DeviceId currentDeviceId,
+                                                TsLoopPacket initPkt,
+                                                TsLoopPacket matchedPkt,
+                                                boolean isFindLoop,
+                                                FlowEntry firstEntry) {
+        OutputInstruction instOutput = (OutputInstruction) instOne;
+        PortNumber instPort = instOutput.port();
+
+        if (!instPort.isLogical()) {
+            // single OUTPUT - NIC or normal port
+
+            Set<Link> dstLink = tsGetEgressLinks(
+                    new ConnectPoint(currentDeviceId, instPort));
+            if (!dstLink.isEmpty()) {
+
+                // TODO - now, just deal with the first destination.
+                // will there be more destinations?
+
+                Link dstThisLink = dstLink.iterator().next();
+                ConnectPoint dstPoint = dstThisLink.dst();
+
+                // check output to devices only (output to a host is normal)
+                if (isDevice(dstPoint)) {
+                    PortCriterion oldInPort =
+                            updatePktInportPerHop(matchedPkt, dstPoint);
+                    matchedPkt.pushPathLink(dstThisLink);
+                    TsLoopPacket newNewPkt = matchedPkt.copyPacketMatch();
+
+                    boolean loopFound =
+                            matchDeviceFlows(dstPoint.deviceId(), newNewPkt);
+                    if (isFindLoop) {
+                        if (loopFound) {
+                            loops.add(newNewPkt);
+                            updateExcludeDeviceSet(newNewPkt);
+                        }
+                        matchedPkt.resetLinkFlow(firstEntry);
+                    } else {
+                        if (loopFound) {
+                            initPkt.handInLoopMatch(newNewPkt);
+                            return true;
+                        }
+                        matchedPkt.popPathLink();
+                    }
+                    restorePktInportPerHop(matchedPkt, oldInPort);
+                }
+            } else {
+                if (!isFindLoop) {
+                    //TODO - NEED
+                    log.warn("no link connecting at device {}, port {}",
+                            currentDeviceId, instPort);
+                }
+            }
+        } else if (instPort.equals(PortNumber.IN_PORT)) {
+            //TODO - in the future,
+            // we may need to resolve this condition 1
+            log.warn("can not handle {} port now.", PortNumber.IN_PORT);
+        } else if (instPort.equals(PortNumber.NORMAL) ||
+                instPort.equals(PortNumber.FLOOD) ||
+                instPort.equals(PortNumber.ALL)) {
+            //TODO - in the future,
+            // we may need to resolve this condition 2
+            log.warn("can not handle {}/{}/{} now.",
+                    PortNumber.NORMAL, PortNumber.FLOOD, PortNumber.ALL);
+        }
+        return false;
+    }
+
+    private void updateExcludeDeviceSet(TsLoopPacket loopPkt) {
+        Iterator<Link> iter = loopPkt.getPathLink();
+        while (iter.hasNext()) {
+            excludeDeviceId.add(iter.next().src().deviceId());
+        }
+    }
+
+    private PortCriterion updatePktInportPerHop(TsLoopPacket oldPkt,
+                                                ConnectPoint dstPoint) {
+
+        PortCriterion inPort = oldPkt.getInport();
+        PortCriterion oldInPort =
+                null != inPort ? (PortCriterion) matchInPort(inPort.port()) : null;
+        // TODO - check - if it really copies this object
+
+        oldPkt.setHeader(matchInPort(dstPoint.port()));
+
+        return oldInPort;
+    }
+
+    private void restorePktInportPerHop(TsLoopPacket pkt,
+                                        PortCriterion oldInPort) {
+        if (oldInPort == null) {
+            pkt.delHeader(IN_PORT);
+        } else {
+            pkt.setHeader(oldInPort);
+        }
+    }
+
+    private void getNetworkSnapshot() {
+        deviceInfo = new HashMap<>();
+        deviceService.getDevices().forEach(d -> deviceInfo.put(d.id(), d));
+
+        flowEntryInfo = new HashMap<>();
+        deviceInfo.keySet().forEach(id ->
+                flowEntryInfo.put(id, flowRuleService.getFlowEntries(id)));
+
+        egressLinkInfo = new HashMap<>();
+        deviceInfo.keySet().forEach(id ->
+                egressLinkInfo.put(id, linkService.getDeviceEgressLinks(id)));
+
+        accessDevices = new HashSet<>();
+        hostService.getHosts().forEach(h ->
+                accessDevices.add(deviceInfo.get(h.location().deviceId())));
+    }
+
+    private Set<Link> tsGetEgressLinks(ConnectPoint point) {
+        Set<Link> portEgressLink = new HashSet<>();
+        DeviceId deviceId = point.deviceId();
+
+        portEgressLink.addAll(
+                //all egress links
+                egressLinkInfo.get(deviceId)
+                        .stream()
+                        .filter(l -> l.src().equals(point))
+                        .collect(Collectors.toList()));
+
+        return portEgressLink;
+    }
+
+    private boolean matchAndAddFlowEntry(FlowEntry flowEntry,
+                                         TsLoopPacket pkt,
+                                         TsReturn<Boolean> isBigger) {
+        isBigger.setValue(false);
+
+        List<Criterion> criterionArray =
+                sortCriteria(flowEntry.selector().criteria());
+
+        for (Criterion criterion : criterionArray) {
+            // TODO - advance
+            switch (criterion.type()) {
+                case IN_PORT:
+                case ETH_SRC:
+                    // At present, not support Ethernet mask (ONOS?)
+                case ETH_DST:
+                    // At present, not support Ethernet mask (ONOS?)
+                case ETH_TYPE:
+                    if (!matchAddExactly(pkt, criterion, isBigger)) {
+                        return false;
+                    }
+                    break;
+                case VLAN_VID:
+                    // At present, not support VLAN mask (ONOS?)
+                case VLAN_PCP:
+                    if (!pkt.headerExists(ETH_TYPE) ||
+                            !((EthTypeCriterion) pkt.getHeader(ETH_TYPE))
+                                    .ethType().equals(VLAN.ethType())) {
+                        return false;
+                    }
+                    if (!matchAddExactly(pkt, criterion, isBigger)) {
+                        return false;
+                    }
+                    break;
+                case IPV4_SRC:
+                case IPV4_DST:
+                    if (!pkt.headerExists(ETH_TYPE) ||
+                            !((EthTypeCriterion) pkt.getHeader(ETH_TYPE))
+                                    .ethType().equals(IPV4.ethType())) {
+                        return false;
+                    }
+                    if (!matchAddIPV4(pkt, criterion, isBigger)) {
+                        return false;
+                    }
+                    break;
+                case IP_PROTO:
+                    if (!pkt.headerExists(ETH_TYPE) ||
+                            !((EthTypeCriterion) pkt.getHeader(ETH_TYPE))
+                                    .ethType().equals(IPV4.ethType())) {
+                        return false;
+                    }
+                    if (!matchAddExactly(pkt, criterion, isBigger)) {
+                        return false;
+                    }
+                    break;
+                case IP_DSCP:
+                    // TODO: 10/28/16 support IP_DSCP match field
+                    break;
+                case IP_ECN:
+                    // TODO: 10/28/16 support IP_DSCP match field
+                    break;
+                case TCP_SRC:
+                case TCP_DST:
+                    if (!pkt.headerExists(IP_PROTO) ||
+                            IP_PROTO_TCP_TS !=
+                                    ((IPProtocolCriterion)
+                                            pkt.getHeader(IP_PROTO))
+                                            .protocol()
+                            ) {
+                        // has TCP match requirement, but can't afford TCP
+                        return false;
+                    }
+                    // in this "for" loop
+                    // avoid IP_PROTO locates after TCP_*
+                    if (!matchAddExactly(pkt, criterion, isBigger)) {
+                        return false;
+                    }
+                    break;
+                case UDP_SRC:
+                case UDP_DST:
+                    if (!pkt.headerExists(IP_PROTO) ||
+                            IP_PROTO_UDP_TS !=
+                                    ((IPProtocolCriterion)
+                                            pkt.getHeader(IP_PROTO))
+                                            .protocol()
+                            ) {
+                        // has UDP match requirement, but can't afford UDP
+                        return false;
+                    }
+                    // in this "for" loop
+                    // avoid IP_PROTO locates after UDP_*
+                    if (!matchAddExactly(pkt, criterion, isBigger)) {
+                        return false;
+                    }
+                    break;
+
+                default:
+                    log.debug("{} can't be supported by OF1.0",
+                            criterion.type());
+                    return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean matchAddExactly(TsLoopPacket pkt,
+                                    Criterion criterion,
+                                    TsReturn<Boolean> isBigger) {
+
+        if (pkt.headerExists(criterion.type())) {
+            if (!pkt.getHeader(criterion.type()).equals(criterion)) {
+                return false;
+            }
+
+        } else {
+            // TODO - check if it is IN_PORT or IN_PHY_PORT, should be strict
+            pkt.setHeader(criterion);
+            isBigger.setValue(true);
+        }
+
+        return true; // should put it here
+    }
+
+    // before invoking this, MUST insure EtherType is IPv4.
+    private boolean matchAddIPV4(TsLoopPacket pkt,
+                                 Criterion criterion,
+                                 TsReturn<Boolean> isBigger) {
+
+        if (pkt.headerExists(criterion.type())) {
+
+            IpPrefix ipFlow = ((IPCriterion) criterion).ip();
+            IpPrefix ipPkt =
+                    ((IPCriterion) pkt.getHeader(criterion.type())).ip();
+
+            // attention - the order below is important
+            if (ipFlow.equals(ipPkt)) {
+                // shoot
+
+            } else if (ipFlow.contains(ipPkt)) {
+                // shoot, pkt is more exact than flowEntry
+
+            } else if (ipPkt.contains(ipFlow)) {
+                // pkt should be changed to be more exact
+                pkt.setHeader(criterion);
+                isBigger.setValue(true);
+            } else {
+                // match fail
+                return false;
+            }
+
+        } else {
+            // attention the order of criteria in "for" loop
+            pkt.setHeader(criterion);
+            isBigger.setValue(true);
+        }
+
+        return true;
+    }
+}
diff --git a/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/NetworkDiagnosticManager.java b/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/NetworkDiagnosticManager.java
new file mode 100644
index 0000000..628d8e6
--- /dev/null
+++ b/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/NetworkDiagnosticManager.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2015-present 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.fnl.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.fnl.intf.NetworkAnomaly;
+import org.onosproject.fnl.intf.NetworkDiagnostic;
+import org.onosproject.fnl.intf.NetworkDiagnostic.Type;
+import org.onosproject.fnl.intf.NetworkDiagnosticService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.link.LinkService;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Default implementation of the Network Troubleshooting Core Service.
+ *
+ * It is simply modularized at present.
+ */
+@Component(immediate = true)
+@Service
+public class NetworkDiagnosticManager implements NetworkDiagnosticService {
+
+    /**
+     * Name of Network Troubleshooting Application.
+     */
+    public static final String NTS_APP_NAME =
+            "org.onosproject.FNL.Network-Troubleshoot";
+
+    private static final String PROPERTY_AUTO_REGISTER_DIAG =
+            "autoRegisterDefaultDiagnostics";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService cfgService;
+
+
+    // ------ service below is for auto register ------
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService ds;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hs;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService frs;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService ls;
+
+
+    @Property(name = PROPERTY_AUTO_REGISTER_DIAG, boolValue = true,
+            label = "Automatically register all of default diagnostic modules.")
+    private boolean autoRegister = true;
+
+
+    private ApplicationId appId;
+
+    private Map<Type, NetworkDiagnostic> diagnostics = new ConcurrentHashMap<>();
+
+
+    @Activate
+    protected void activate(ComponentContext context) {
+        appId = coreService.registerApplication(NTS_APP_NAME);
+
+        cfgService.registerProperties(getClass());
+        readConfiguration(context);
+
+        autoRegisterDiagnostics();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        unregisterDiagnostics();
+        log.info("Stopped");
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+
+        readConfiguration(context);
+
+        // We should not register default diagnostics automatically
+        // in the Modify(), because that will erase the result of
+        // dynamic extension, run-time and custom diagnostics.
+        //
+        // And, using this modified() is to avoid deactivate-activate procedure.
+
+        log.info("Modified");
+    }
+
+    private void readConfiguration(ComponentContext context) {
+        Dictionary<?, ?> properties =  context.getProperties();
+
+        Boolean autoRegisterEnabled =
+                Tools.isPropertyEnabled(properties, PROPERTY_AUTO_REGISTER_DIAG);
+        if (autoRegisterEnabled == null) {
+            log.warn("Auto Register is not configured, " +
+                    "using current value of {}", autoRegister);
+        } else {
+            autoRegister = autoRegisterEnabled;
+            log.info("Configured. Auto Register is {}",
+                    autoRegister ? "enabled" : "disabled");
+        }
+    }
+
+    private void autoRegisterDiagnostics() {
+        if (!autoRegister) {
+            return;
+        }
+
+        // TODO: 10/26/16 future new default diagnostics should be added here.
+        register(new DefaultCheckLoop(ds, hs, frs, ls));
+    }
+
+    private void unregisterDiagnostics() {
+        // TODO: 10/27/16 improve this for parallel.
+        diagnostics.clear();
+    }
+
+    @Override
+    public Set<NetworkAnomaly> findAnomalies() {
+        Set<NetworkAnomaly> allAnomalies = new HashSet<>();
+
+        diagnostics.forEach((type, diag) ->
+                allAnomalies.addAll(diag.findAnomalies()));
+
+        return allAnomalies;
+    }
+
+    @Override
+    public Set<NetworkAnomaly> findAnomalies(Type type) {
+        checkNotNull(type, "NetworkAnomaly Type cannot be null");
+
+        Set<NetworkAnomaly> anomalies = new HashSet<>();
+
+        NetworkDiagnostic diag = diagnostics.get(type);
+        if (diag == null) {
+            log.warn("no anomalies of type {} found", type);
+            return anomalies;
+        }
+
+        anomalies.addAll(diag.findAnomalies());
+
+        return anomalies;
+    }
+
+    @Override
+    public void register(NetworkDiagnostic diag) {
+        checkNotNull(diag, "Diagnostic cannot be null");
+
+        NetworkDiagnostic oldDiag = diagnostics.put(diag.type(), diag);
+
+        if (oldDiag != null) {
+            log.warn("previous diagnostic {} is replaced by {},",
+                    oldDiag.getClass(), diag.getClass());
+        } else {
+            log.info("Register {} type module: {}",
+                    diag.type(), diag.getClass().getName());
+        }
+    }
+
+    @Override
+    public boolean unregister(NetworkDiagnostic diag) {
+        checkNotNull(diag, "Diagnostic cannot be null");
+
+        return diagnostics.remove(diag.type(), diag);
+    }
+}
diff --git a/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/package-info.java b/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/package-info.java
new file mode 100644
index 0000000..c02b757
--- /dev/null
+++ b/apps/network-troubleshoot/core/src/main/java/org/onosproject/fnl/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present 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.
+ */
+
+/**
+ * The Implement of troubleshoot Core and specific checking modules.
+ */
+package org.onosproject.fnl.impl;
diff --git a/apps/network-troubleshoot/core/src/test/java/org/onosproject/fnl/impl/DefaultCheckLoopTest.java b/apps/network-troubleshoot/core/src/test/java/org/onosproject/fnl/impl/DefaultCheckLoopTest.java
new file mode 100644
index 0000000..3c72a09
--- /dev/null
+++ b/apps/network-troubleshoot/core/src/test/java/org/onosproject/fnl/impl/DefaultCheckLoopTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2015-present 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.fnl.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.fnl.intf.NetworkAnomaly;
+import org.onosproject.fnl.intf.NetworkDiagnostic;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.link.LinkService;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.onlab.packet.EthType.EtherType.IPV4;
+import static org.onlab.packet.TpPort.tpPort;
+import static org.onosproject.net.DefaultAnnotations.EMPTY;
+import static org.onosproject.net.Link.State.ACTIVE;
+import static org.onosproject.net.Link.Type.DIRECT;
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
+import static org.onosproject.net.provider.ProviderId.NONE;
+
+/**
+ * Unit Tests for DefaultCheckLoop class.
+ */
+public class DefaultCheckLoopTest {
+
+    private NetworkDiagnostic defaultCheckLoop;
+
+    private DeviceService ds;
+    private HostService hs;
+    private FlowRuleService frs;
+    private LinkService ls;
+
+    private List<Device> devices;
+    private List<Host> hosts;
+    private Map<DeviceId, Set<Link>> egressLinks;
+    private Map<DeviceId, List<FlowEntry>> flowEntries;
+
+
+    private static final DeviceId DEVICE_ID_A = DeviceId.deviceId("of:000000000000000A");
+    private static final DeviceId DEVICE_ID_B = DeviceId.deviceId("of:000000000000000B");
+    private static final DeviceId DEVICE_ID_C = DeviceId.deviceId("of:000000000000000C");
+    private static final DeviceId DEVICE_ID_D = DeviceId.deviceId("of:000000000000000D");
+    private static final DeviceId DEVICE_ID_E = DeviceId.deviceId("of:E000000000000000");
+    private static final DeviceId DEVICE_ID_F = DeviceId.deviceId("of:F000000000000000");
+    private static final DeviceId DEVICE_ID_G = DeviceId.deviceId("of:A000000000000000");
+
+    private static final String HOSTID_EXAMPLE = "12:34:56:78:9A:BC/1355";
+    private static final long FLOWRULE_COOKIE_EXAMPLE = 708;
+    private static final int FLOWRULE_PRIORITY_EXAMPLE = 738;
+
+
+    @Before
+    public void setUp() {
+        ds = EasyMock.createMock(DeviceService.class);
+        hs = EasyMock.createMock(HostService.class);
+        frs = EasyMock.createMock(FlowRuleService.class);
+        ls = EasyMock.createMock(LinkService.class);
+
+        defaultCheckLoop = new DefaultCheckLoop(ds, hs, frs, ls);
+    }
+
+    @After
+    public void tearDown() {
+        // do nothing
+    }
+
+    @Test
+    public void testFindLoops() {
+        produceTopoDevices();
+        produceTopoHosts();
+        produceTopoLinks();
+        produceFlowEntries();
+
+        initMock();
+
+        Set<NetworkAnomaly> loops = defaultCheckLoop.findAnomalies();
+        assertThat(loops, hasSize(1));
+    }
+
+    private void initMock() {
+        expect(ds.getDevices()).andReturn(devices).anyTimes();
+        replay(ds);
+
+        expect(hs.getHosts()).andReturn(hosts).anyTimes();
+        replay(hs);
+
+        // --- init flow rule service ---
+
+        expect(frs.getFlowEntries(DEVICE_ID_A))
+                .andReturn(flowEntries.get(DEVICE_ID_A)).anyTimes();
+        expect(frs.getFlowEntries(DEVICE_ID_B))
+                .andReturn(flowEntries.get(DEVICE_ID_B)).anyTimes();
+        expect(frs.getFlowEntries(DEVICE_ID_C))
+                .andReturn(flowEntries.get(DEVICE_ID_C)).anyTimes();
+        expect(frs.getFlowEntries(DEVICE_ID_D))
+                .andReturn(flowEntries.get(DEVICE_ID_D)).anyTimes();
+        expect(frs.getFlowEntries(DEVICE_ID_E))
+                .andReturn(flowEntries.get(DEVICE_ID_E)).anyTimes();
+        expect(frs.getFlowEntries(DEVICE_ID_F))
+                .andReturn(flowEntries.get(DEVICE_ID_F)).anyTimes();
+        expect(frs.getFlowEntries(DEVICE_ID_G))
+                .andReturn(flowEntries.get(DEVICE_ID_G)).anyTimes();
+        replay(frs);
+
+        // --- init link service ---
+
+        expect(ls.getDeviceEgressLinks(DEVICE_ID_A))
+                .andReturn(egressLinks.get(DEVICE_ID_A)).anyTimes();
+        expect(ls.getDeviceEgressLinks(DEVICE_ID_B))
+                .andReturn(egressLinks.get(DEVICE_ID_B)).anyTimes();
+        expect(ls.getDeviceEgressLinks(DEVICE_ID_C))
+                .andReturn(egressLinks.get(DEVICE_ID_C)).anyTimes();
+        expect(ls.getDeviceEgressLinks(DEVICE_ID_D))
+                .andReturn(egressLinks.get(DEVICE_ID_D)).anyTimes();
+        expect(ls.getDeviceEgressLinks(DEVICE_ID_E))
+                .andReturn(egressLinks.get(DEVICE_ID_E)).anyTimes();
+        expect(ls.getDeviceEgressLinks(DEVICE_ID_F))
+                .andReturn(egressLinks.get(DEVICE_ID_F)).anyTimes();
+        expect(ls.getDeviceEgressLinks(DEVICE_ID_G))
+                .andReturn(egressLinks.get(DEVICE_ID_G)).anyTimes();
+        replay(ls);
+    }
+
+    private void produceTopoDevices() {
+        devices = new ArrayList<>();
+        devices.add(produceOneDevice(DEVICE_ID_A));
+        devices.add(produceOneDevice(DEVICE_ID_B));
+        devices.add(produceOneDevice(DEVICE_ID_C));
+        devices.add(produceOneDevice(DEVICE_ID_D));
+        devices.add(produceOneDevice(DEVICE_ID_E));
+        devices.add(produceOneDevice(DEVICE_ID_F));
+        devices.add(produceOneDevice(DEVICE_ID_G));
+    }
+
+    private Device produceOneDevice(DeviceId dpid) {
+        return new DefaultDevice(NONE, dpid,
+                Device.Type.SWITCH, "", "", "", "", new ChassisId(),
+                EMPTY);
+    }
+
+    private void produceTopoHosts() {
+        hosts = new ArrayList<>();
+        hosts.add(produceOneHost(DEVICE_ID_A, 3));
+        hosts.add(produceOneHost(DEVICE_ID_B, 3));
+        hosts.add(produceOneHost(DEVICE_ID_D, 3));
+        hosts.add(produceOneHost(DEVICE_ID_F, 2));
+        hosts.add(produceOneHost(DEVICE_ID_F, 3));
+        hosts.add(produceOneHost(DEVICE_ID_G, 1));
+        hosts.add(produceOneHost(DEVICE_ID_G, 3));
+    }
+
+    private Host produceOneHost(DeviceId dpid, int port) {
+        return new DefaultHost(NONE, HostId.hostId(HOSTID_EXAMPLE),
+                MacAddress.valueOf(0), VlanId.vlanId(),
+                new HostLocation(dpid, portNumber(port), 1),
+                ImmutableSet.of(), EMPTY);
+    }
+
+    private void produceTopoLinks() {
+        egressLinks = new HashMap<>();
+
+        Set<Link> el = new HashSet<>();
+        el.add(produceOneEgressLink(DEVICE_ID_A, 1, DEVICE_ID_B, 1));
+        el.add(produceOneEgressLink(DEVICE_ID_A, 2, DEVICE_ID_D, 2));
+        egressLinks.put(DEVICE_ID_A, el);
+
+        el = new HashSet<>();
+        el.add(produceOneEgressLink(DEVICE_ID_B, 1, DEVICE_ID_A, 1));
+        el.add(produceOneEgressLink(DEVICE_ID_B, 2, DEVICE_ID_C, 2));
+        egressLinks.put(DEVICE_ID_B, el);
+
+        el = new HashSet<>();
+        el.add(produceOneEgressLink(DEVICE_ID_C, 1, DEVICE_ID_D, 1));
+        el.add(produceOneEgressLink(DEVICE_ID_C, 2, DEVICE_ID_B, 2));
+        el.add(produceOneEgressLink(DEVICE_ID_C, 3, DEVICE_ID_E, 3));
+        egressLinks.put(DEVICE_ID_C, el);
+
+        el = new HashSet<>();
+        el.add(produceOneEgressLink(DEVICE_ID_D, 1, DEVICE_ID_C, 1));
+        el.add(produceOneEgressLink(DEVICE_ID_D, 2, DEVICE_ID_A, 2));
+        egressLinks.put(DEVICE_ID_D, el);
+
+        el = new HashSet<>();
+        el.add(produceOneEgressLink(DEVICE_ID_E, 1, DEVICE_ID_F, 1));
+        el.add(produceOneEgressLink(DEVICE_ID_E, 2, DEVICE_ID_G, 2));
+        el.add(produceOneEgressLink(DEVICE_ID_E, 3, DEVICE_ID_C, 3));
+        egressLinks.put(DEVICE_ID_E, el);
+
+        el = new HashSet<>();
+        el.add(produceOneEgressLink(DEVICE_ID_F, 1, DEVICE_ID_E, 1));
+        egressLinks.put(DEVICE_ID_F, el);
+
+        el = new HashSet<>();
+        el.add(produceOneEgressLink(DEVICE_ID_G, 2, DEVICE_ID_E, 2));
+        egressLinks.put(DEVICE_ID_G, el);
+    }
+
+    private Link produceOneEgressLink(DeviceId srcId, int srcPort, DeviceId dstId, int dstPort) {
+        return DefaultLink.builder()
+                .providerId(NONE)
+                .src(new ConnectPoint(srcId, portNumber(srcPort)))
+                .dst(new ConnectPoint(dstId, portNumber(dstPort)))
+                .type(DIRECT)
+                .state(ACTIVE)
+                .build();
+    }
+
+    private void produceFlowEntries() {
+        flowEntries = new HashMap<>();
+
+        List<FlowEntry> fe = new ArrayList<>();
+        fe.add(produceOneFlowEntry(DEVICE_ID_A, 2));
+        flowEntries.put(DEVICE_ID_A, fe);
+
+        fe = new ArrayList<>();
+        fe.add(produceOneFlowEntry(DEVICE_ID_D, 1));
+        flowEntries.put(DEVICE_ID_D, fe);
+
+        fe = new ArrayList<>();
+        fe.add(produceOneFlowEntry(DEVICE_ID_C, 2));
+        flowEntries.put(DEVICE_ID_C, fe);
+
+        fe = new ArrayList<>();
+        fe.add(produceOneFlowEntry(DEVICE_ID_B, 1));
+        flowEntries.put(DEVICE_ID_B, fe);
+
+        flowEntries.put(DEVICE_ID_E, new ArrayList<>());
+        flowEntries.put(DEVICE_ID_F, new ArrayList<>());
+        flowEntries.put(DEVICE_ID_G, new ArrayList<>());
+
+    }
+
+    private FlowEntry produceOneFlowEntry(DeviceId dpid, int outPort) {
+
+        TrafficSelector.Builder sb = DefaultTrafficSelector.builder()
+                .matchEthType(IPV4.ethType().toShort())
+                .matchIPDst(IpPrefix.valueOf("10.0.0.0/8"))
+                .matchIPProtocol(IPv4.PROTOCOL_TCP)
+                .matchTcpDst(tpPort(22));
+
+        TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder()
+                .setOutput(portNumber(outPort));
+
+        return new DefaultFlowEntry(DefaultFlowRule.builder()
+                .withPriority(FLOWRULE_PRIORITY_EXAMPLE).forDevice(dpid).forTable(0)
+                .withCookie(FLOWRULE_COOKIE_EXAMPLE)
+                .withSelector(sb.build()).withTreatment(tb.build())
+                .makePermanent().build(), ADDED);
+    }
+}